remove shisui

Signed-off-by: Chen Kai <281165273grape@gmail.com>
This commit is contained in:
Chen Kai 2024-12-08 22:36:41 +08:00
parent b209acdc7d
commit 58195822c4
86 changed files with 0 additions and 152460 deletions

View file

@ -1,70 +0,0 @@
package main
import (
"flag"
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
func TestGenConfig(t *testing.T) {
size := uint64(5 * 1000 * 1000 * 1000)
flagSet := flag.NewFlagSet("test", 0)
flagSet.String("rpc.addr", "127.0.0.11", "test")
flagSet.String("rpc.port", "8888", "test")
tmpDir := t.TempDir()
flagSet.String("data.dir", tmpDir, "test")
flagSet.Uint64("data.capacity", size, "test")
// flagSet.String("udp.addr", "172.23.50.11", "test")
flagSet.Int("udp.port", 9999, "test")
flagSet.Int("loglevel", 3, "test")
val := cli.NewStringSlice("history")
flagSet.Var(val, "networks", "test")
command := &cli.Command{Name: "mycommand"}
ctx := cli.NewContext(nil, flagSet, nil)
ctx.Command = command
config, err := getPortalConfig(ctx)
require.NoError(t, err)
require.Equal(t, config.DataCapacity, size)
require.Equal(t, config.DataDir, tmpDir)
require.Equal(t, config.LogLevel, 3)
// require.Equal(t, config.RpcAddr, "127.0.0.11:8888")
require.Equal(t, config.Protocol.ListenAddr, ":9999")
require.Equal(t, config.Networks, []string{"history"})
}
func TestKeyConfig(t *testing.T) {
flagSet := flag.NewFlagSet("test", 0)
tmpDir := t.TempDir()
flagSet.String("data.dir", tmpDir, "test")
pk := "a19d7a264e68004832327fca0ac46636332e0ec4b2a20a7ac942020754fcb666"
flagSet.String("private.key", "0x"+pk, "test")
command := &cli.Command{Name: "mycommand"}
ctx := cli.NewContext(nil, flagSet, nil)
ctx.Command = command
config, err := getPortalConfig(ctx)
require.NoError(t, err)
require.Equal(t, config.DataDir, tmpDir)
keyPk, err := crypto.HexToECDSA(pk)
require.Nil(t, err)
require.Equal(t, config.PrivateKey, keyPk)
fullPath := filepath.Join(config.DataDir, privateKeyFileName)
keyStored, err := os.ReadFile(fullPath)
require.Nil(t, err)
keyEnc := string(keyStored)
require.Equal(t, keyEnc, pk)
}

View file

@ -1,653 +0,0 @@
package main
import (
"context"
"crypto/ecdsa"
"database/sql"
"encoding/hex"
"fmt"
"net"
"net/http"
"os/signal"
"path"
"path/filepath"
"slices"
"strings"
"syscall"
"time"
"os"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/portalnetwork/beacon"
"github.com/ethereum/go-ethereum/portalnetwork/ethapi"
"github.com/ethereum/go-ethereum/portalnetwork/history"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/state"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/ethereum/go-ethereum/portalnetwork/web3"
"github.com/ethereum/go-ethereum/rpc"
"github.com/mattn/go-isatty"
_ "github.com/mattn/go-sqlite3"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/urfave/cli/v2"
)
var (
storageCapacity metrics.Gauge
)
const (
privateKeyFileName = "clientKey"
)
type Config struct {
Protocol *portalwire.PortalProtocolConfig
PrivateKey *ecdsa.PrivateKey
RpcAddr string
DataDir string
DataCapacity uint64
LogLevel int
Networks []string
}
type Client struct {
DiscV5API *portalwire.DiscV5API
HistoryNetwork *history.Network
BeaconNetwork *beacon.BeaconNetwork
StateNetwork *state.StateNetwork
Server *http.Server
}
var app = flags.NewApp("the go-portal-network command line interface")
var (
portalProtocolFlags = []cli.Flag{
utils.PortalNATFlag,
utils.PortalUDPPortFlag,
utils.PortalBootNodesFlag,
utils.PortalPrivateKeyFlag,
utils.PortalNetworksFlag,
}
historyRpcFlags = []cli.Flag{
utils.PortalRPCListenAddrFlag,
utils.PortalRPCPortFlag,
utils.PortalDataDirFlag,
utils.PortalDataCapacityFlag,
utils.PortalLogLevelFlag,
utils.PortalLogFormatFlag,
}
metricsFlags = []cli.Flag{
utils.MetricsEnabledFlag,
utils.MetricsHTTPFlag,
utils.MetricsPortFlag,
utils.MetricsEnableInfluxDBFlag,
utils.MetricsInfluxDBEndpointFlag,
utils.MetricsInfluxDBDatabaseFlag,
utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBTagsFlag,
utils.MetricsEnableInfluxDBV2Flag,
utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag,
}
)
func init() {
app.Action = shisui
app.Flags = slices.Concat(portalProtocolFlags, historyRpcFlags, metricsFlags, debug.Flags)
flags.AutoEnvVars(app.Flags, "SHISUI")
}
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func shisui(ctx *cli.Context) error {
err := setDefaultLogger(ctx.Int(utils.PortalLogLevelFlag.Name), ctx.String(utils.PortalLogFormatFlag.Name))
if err != nil {
return err
}
// Start metrics export if enabled
utils.SetupMetrics(ctx)
// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)
go portalwire.CollectPortalMetrics(5*time.Second, ctx.StringSlice(utils.PortalNetworksFlag.Name), ctx.String(utils.PortalDataDirFlag.Name))
if metrics.Enabled {
storageCapacity = metrics.NewRegisteredGauge("portal/storage_capacity", nil)
storageCapacity.Update(ctx.Int64(utils.PortalDataCapacityFlag.Name))
}
config, err := getPortalConfig(ctx)
if err != nil {
return nil
}
clientChan := make(chan *Client, 1)
go handlerInterrupt(clientChan)
addr, err := net.ResolveUDPAddr("udp", config.Protocol.ListenAddr)
if err != nil {
return err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
return startPortalRpcServer(*config, conn, config.RpcAddr, clientChan)
}
func setDefaultLogger(logLevel int, logFormat string) error {
var glogger *log.GlogHandler
switch {
case logFormat == "json":
glogger = log.NewGlogHandler(log.JSONHandler(os.Stderr))
case logFormat == "logfmt":
glogger = log.NewGlogHandler(log.LogfmtHandler(os.Stderr))
case logFormat == "", logFormat == "terminal":
useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
glogger = log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, useColor))
default:
// Unknown log format specified
return fmt.Errorf("unknown log format: %v", logFormat)
}
slogVerbosity := log.FromLegacyLevel(logLevel)
glogger.Verbosity(slogVerbosity)
defaultLogger := log.NewLogger(glogger)
log.SetDefault(defaultLogger)
return nil
}
func handlerInterrupt(clientChan <-chan *Client) {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(interrupt)
<-interrupt
log.Warn("Closing Shisui gracefully (type CTRL-C again to force quit)")
go func() {
if len(clientChan) == 0 {
log.Warn("Waiting for the client to start...")
}
c := <-clientChan
c.closePortalRpcServer()
}()
<-interrupt
os.Exit(1)
}
func (cli *Client) closePortalRpcServer() {
if cli.HistoryNetwork != nil {
log.Info("Closing history network...")
cli.HistoryNetwork.Stop()
}
if cli.BeaconNetwork != nil {
log.Info("Closing beacon network...")
cli.BeaconNetwork.Stop()
}
if cli.StateNetwork != nil {
log.Info("Closing state network...")
cli.StateNetwork.Stop()
}
log.Info("Closing Database...")
cli.DiscV5API.DiscV5.LocalNode().Database().Close()
log.Info("Closing UDPv5 protocol...")
cli.DiscV5API.DiscV5.Close()
log.Info("Closing servers...")
cli.Server.Close()
os.Exit(1)
}
func startPortalRpcServer(config Config, conn discover.UDPConn, addr string, clientChan chan<- *Client) error {
client := &Client{}
discV5, localNode, err := initDiscV5(config, conn)
if err != nil {
return err
}
server := rpc.NewServer()
discV5API := portalwire.NewDiscV5API(discV5)
err = server.RegisterName("discv5", discV5API)
if err != nil {
return err
}
client.DiscV5API = discV5API
api := &web3.API{}
err = server.RegisterName("web3", api)
if err != nil {
return err
}
utp := portalwire.NewPortalUtp(context.Background(), config.Protocol, discV5, conn)
var historyNetwork *history.Network
if slices.Contains(config.Networks, portalwire.History.Name()) {
historyNetwork, err = initHistory(config, server, conn, localNode, discV5, utp)
if err != nil {
return err
}
client.HistoryNetwork = historyNetwork
}
var beaconNetwork *beacon.BeaconNetwork
if slices.Contains(config.Networks, portalwire.Beacon.Name()) {
beaconNetwork, err = initBeacon(config, server, conn, localNode, discV5, utp)
if err != nil {
return err
}
client.BeaconNetwork = beaconNetwork
}
var stateNetwork *state.StateNetwork
if slices.Contains(config.Networks, portalwire.State.Name()) {
stateNetwork, err = initState(config, server, conn, localNode, discV5, utp)
if err != nil {
return err
}
client.StateNetwork = stateNetwork
}
ethapi := &ethapi.API{
History: historyNetwork,
//static configuration of ChainId, currently only mainnet implemented
ChainID: core.DefaultGenesisBlock().Config.ChainID,
}
err = server.RegisterName("eth", ethapi)
if err != nil {
return err
}
httpServer := &http.Server{
Addr: addr,
Handler: server,
}
client.Server = httpServer
clientChan <- client
return httpServer.ListenAndServe()
}
func initDiscV5(config Config, conn discover.UDPConn) (*discover.UDPv5, *enode.LocalNode, error) {
discCfg := discover.Config{
PrivateKey: config.PrivateKey,
NetRestrict: config.Protocol.NetRestrict,
Bootnodes: config.Protocol.BootstrapNodes,
Log: log.New("protocol", "discV5"),
}
nodeDB, err := enode.OpenDB(config.Protocol.NodeDBPath)
if err != nil {
return nil, nil, err
}
localNode := enode.NewLocalNode(nodeDB, config.PrivateKey)
localNode.Set(portalwire.Tag)
listenerAddr := conn.LocalAddr().(*net.UDPAddr)
nat := config.Protocol.NAT
if nat != nil && !listenerAddr.IP.IsLoopback() {
doPortMapping(nat, localNode, listenerAddr)
}
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, nil, err
}
return discV5, localNode, nil
}
func doPortMapping(natm nat.Interface, ln *enode.LocalNode, addr *net.UDPAddr) {
const (
protocol = "udp"
name = "ethereum discovery"
)
var (
intport = addr.Port
extaddr = &net.UDPAddr{IP: addr.IP, Port: addr.Port}
mapTimeout = nat.DefaultMapTimeout
)
addMapping := func() {
// Get the external address.
var err error
extaddr.IP, err = natm.ExternalIP()
if err != nil {
log.Debug("Couldn't get external IP", "err", err)
return
}
// Create the mapping.
p, err := natm.AddMapping(protocol, extaddr.Port, intport, name, mapTimeout)
if err != nil {
log.Debug("Couldn't add port mapping", "err", err)
return
}
if p != uint16(extaddr.Port) {
extaddr.Port = int(p)
log.Info("NAT mapped alternative port")
} else {
log.Info("NAT mapped port")
}
// Update IP/port information of the local node.
ln.SetStaticIP(extaddr.IP)
ln.SetFallbackUDP(extaddr.Port)
}
// Perform mapping once, synchronously.
log.Info("Attempting port mapping")
addMapping()
// Refresh the mapping periodically.
go func() {
refresh := time.NewTimer(mapTimeout)
defer refresh.Stop()
for range refresh.C {
addMapping()
refresh.Reset(mapTimeout)
}
}()
}
func initHistory(config Config, server *rpc.Server, conn discover.UDPConn, localNode *enode.LocalNode, discV5 *discover.UDPv5, utp *portalwire.PortalUtp) (*history.Network, error) {
networkName := portalwire.History.Name()
db, err := history.NewDB(config.DataDir, networkName)
if err != nil {
return nil, err
}
contentStorage, err := history.NewHistoryStorage(storage.PortalStorageConfig{
StorageCapacityMB: config.DataCapacity,
DB: db,
NodeId: localNode.ID(),
NetworkName: networkName,
})
if err != nil {
return nil, err
}
contentQueue := make(chan *portalwire.ContentElement, 50)
protocol, err := portalwire.NewPortalProtocol(
config.Protocol,
portalwire.History,
config.PrivateKey,
conn,
localNode,
discV5,
utp,
contentStorage,
contentQueue)
if err != nil {
return nil, err
}
historyAPI := portalwire.NewPortalAPI(protocol)
historyNetworkAPI := history.NewHistoryNetworkAPI(historyAPI)
err = server.RegisterName("portal", historyNetworkAPI)
if err != nil {
return nil, err
}
accumulator, err := history.NewMasterAccumulator()
if err != nil {
return nil, err
}
historyNetwork := history.NewHistoryNetwork(protocol, &accumulator)
return historyNetwork, historyNetwork.Start()
}
func initBeacon(config Config, server *rpc.Server, conn discover.UDPConn, localNode *enode.LocalNode, discV5 *discover.UDPv5, utp *portalwire.PortalUtp) (*beacon.BeaconNetwork, error) {
dbPath := path.Join(config.DataDir, "beacon")
err := os.MkdirAll(dbPath, 0755)
if err != nil {
return nil, err
}
sqlDb, err := sql.Open("sqlite3", path.Join(dbPath, "beacon.sqlite"))
if err != nil {
return nil, err
}
contentStorage, err := beacon.NewBeaconStorage(storage.PortalStorageConfig{
StorageCapacityMB: config.DataCapacity,
DB: sqlDb,
NodeId: localNode.ID(),
Spec: configs.Mainnet,
NetworkName: portalwire.Beacon.Name(),
})
if err != nil {
return nil, err
}
contentQueue := make(chan *portalwire.ContentElement, 50)
protocol, err := portalwire.NewPortalProtocol(
config.Protocol,
portalwire.Beacon,
config.PrivateKey,
conn,
localNode,
discV5,
utp,
contentStorage,
contentQueue)
if err != nil {
return nil, err
}
portalApi := portalwire.NewPortalAPI(protocol)
beaconAPI := beacon.NewBeaconNetworkAPI(portalApi)
err = server.RegisterName("portal", beaconAPI)
if err != nil {
return nil, err
}
beaconNetwork := beacon.NewBeaconNetwork(protocol)
return beaconNetwork, beaconNetwork.Start()
}
func initState(config Config, server *rpc.Server, conn discover.UDPConn, localNode *enode.LocalNode, discV5 *discover.UDPv5, utp *portalwire.PortalUtp) (*state.StateNetwork, error) {
networkName := portalwire.State.Name()
db, err := history.NewDB(config.DataDir, networkName)
if err != nil {
return nil, err
}
contentStorage, err := history.NewHistoryStorage(storage.PortalStorageConfig{
StorageCapacityMB: config.DataCapacity,
DB: db,
NodeId: localNode.ID(),
NetworkName: networkName,
})
if err != nil {
return nil, err
}
stateStore := state.NewStateStorage(contentStorage, db)
contentQueue := make(chan *portalwire.ContentElement, 50)
protocol, err := portalwire.NewPortalProtocol(
config.Protocol,
portalwire.State,
config.PrivateKey,
conn,
localNode,
discV5,
utp,
stateStore,
contentQueue)
if err != nil {
return nil, err
}
api := portalwire.NewPortalAPI(protocol)
stateNetworkAPI := state.NewStateNetworkAPI(api)
err = server.RegisterName("portal", stateNetworkAPI)
if err != nil {
return nil, err
}
client := rpc.DialInProc(server)
historyNetwork := state.NewStateNetwork(protocol, client)
return historyNetwork, historyNetwork.Start()
}
func getPortalConfig(ctx *cli.Context) (*Config, error) {
config := &Config{
Protocol: portalwire.DefaultPortalProtocolConfig(),
}
httpAddr := ctx.String(utils.PortalRPCListenAddrFlag.Name)
httpPort := ctx.String(utils.PortalRPCPortFlag.Name)
config.RpcAddr = net.JoinHostPort(httpAddr, httpPort)
config.DataDir = ctx.String(utils.PortalDataDirFlag.Name)
config.DataCapacity = ctx.Uint64(utils.PortalDataCapacityFlag.Name)
config.LogLevel = ctx.Int(utils.PortalLogLevelFlag.Name)
port := ctx.String(utils.PortalUDPPortFlag.Name)
if !strings.HasPrefix(port, ":") {
config.Protocol.ListenAddr = ":" + port
} else {
config.Protocol.ListenAddr = port
}
err := setPrivateKey(ctx, config)
if err != nil {
return config, err
}
natString := ctx.String(utils.PortalNATFlag.Name)
if natString != "" {
natInterface, err := nat.Parse(natString)
if err != nil {
return config, err
}
config.Protocol.NAT = natInterface
}
setPortalBootstrapNodes(ctx, config)
config.Networks = ctx.StringSlice(utils.PortalNetworksFlag.Name)
return config, nil
}
func setPrivateKey(ctx *cli.Context, config *Config) error {
var privateKey *ecdsa.PrivateKey
var err error
keyStr := ctx.String(utils.PortalPrivateKeyFlag.Name)
if keyStr != "" {
keyBytes, err := hexutil.Decode(keyStr)
if err != nil {
return err
}
privateKey, err = crypto.ToECDSA(keyBytes)
if err != nil {
return err
}
} else {
fullPath := filepath.Join(config.DataDir, privateKeyFileName)
if _, err := os.Stat(fullPath); err == nil {
log.Info("Loading private key from file", "datadir", config.DataDir, "file", privateKeyFileName)
privateKey, err = readPrivateKey(config, privateKeyFileName)
if err != nil {
return err
}
} else {
if os.IsNotExist(err) {
err := os.MkdirAll(config.DataDir, os.ModePerm)
if err != nil {
log.Error("Failed to create directory:", "err", err)
}
file, err := os.Create(fullPath)
if err != nil {
log.Error("Failed to create file:", "err", err)
}
defer file.Close()
}
log.Info("Creating new private key")
privateKey, err = crypto.GenerateKey()
if err != nil {
return err
}
}
}
config.PrivateKey = privateKey
err = writePrivateKey(privateKey, config, privateKeyFileName)
if err != nil {
return err
}
return nil
}
func writePrivateKey(privateKey *ecdsa.PrivateKey, config *Config, fileName string) error {
keyEnc := hex.EncodeToString(crypto.FromECDSA(privateKey))
fullPath := filepath.Join(config.DataDir, fileName)
file, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(keyEnc)
if err != nil {
return err
}
return nil
}
func readPrivateKey(config *Config, fileName string) (*ecdsa.PrivateKey, error) {
fullPath := filepath.Join(config.DataDir, fileName)
keyBytes, err := os.ReadFile(fullPath)
if err != nil {
return nil, err
}
keyEnc := string(keyBytes)
key, err := crypto.HexToECDSA(keyEnc)
if err != nil {
return nil, err
}
return key, nil
}
// setPortalBootstrapNodes creates a list of bootstrap nodes from the command line
// flags, reverting to pre-configured ones if none have been specified.
func setPortalBootstrapNodes(ctx *cli.Context, config *Config) {
urls := params.PortalBootnodes
if ctx.IsSet(utils.PortalBootNodesFlag.Name) {
flag := ctx.String(utils.PortalBootNodesFlag.Name)
if flag == "none" {
return
}
urls = utils.SplitAndTrim(flag)
}
for _, url := range urls {
if url != "" {
node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
continue
}
config.Protocol.BootstrapNodes = append(config.Protocol.BootstrapNodes, node)
}
}
}

View file

@ -1,33 +0,0 @@
package main
import (
"net"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/stretchr/testify/assert"
)
func newLocalNodeForTesting() (*enode.LocalNode, *enode.DB) {
db, _ := enode.OpenDB("")
key, _ := crypto.GenerateKey()
return enode.NewLocalNode(db, key), db
}
func TestDoPortMapping(t *testing.T) {
nat := nat.ExtIP{33, 44, 55, 66}
localNode, _ := newLocalNodeForTesting()
listenerAddr := &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1234}
doPortMapping(nat, localNode, listenerAddr)
assert.Equal(t, localNode.Seq(), uint64(1))
assert.Equal(t, localNode.Node().IP(), net.IP{33, 44, 55, 66})
assert.Equal(t, localNode.Node().UDP(), 1234)
assert.Equal(t, localNode.Node().TCP(), 0)
_ = localNode.Node().UDP()
assert.Equal(t, localNode.Seq(), uint64(2))
}

View file

@ -1,75 +0,0 @@
package beacon
import (
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
)
type API struct {
*portalwire.PortalProtocolAPI
}
func (p *API) BeaconRoutingTableInfo() *portalwire.RoutingTableInfo {
return p.RoutingTableInfo()
}
func (p *API) BeaconAddEnr(enr string) (bool, error) {
return p.AddEnr(enr)
}
func (p *API) BeaconGetEnr(nodeId string) (string, error) {
return p.GetEnr(nodeId)
}
func (p *API) BeaconDeleteEnr(nodeId string) (bool, error) {
return p.DeleteEnr(nodeId)
}
func (p *API) BeaconLookupEnr(nodeId string) (string, error) {
return p.LookupEnr(nodeId)
}
func (p *API) BeaconPing(enr string) (*portalwire.PortalPongResp, error) {
return p.Ping(enr)
}
func (p *API) BeaconFindNodes(enr string, distances []uint) ([]string, error) {
return p.FindNodes(enr, distances)
}
func (p *API) BeaconFindContent(enr string, contentKey string) (interface{}, error) {
return p.FindContent(enr, contentKey)
}
func (p *API) BeaconOffer(enr string, contentItems [][2]string) (string, error) {
return p.Offer(enr, contentItems)
}
func (p *API) BeaconRecursiveFindNodes(nodeId string) ([]string, error) {
return p.RecursiveFindNodes(nodeId)
}
func (p *API) BeaconGetContent(contentKeyHex string) (*portalwire.ContentInfo, error) {
return p.RecursiveFindContent(contentKeyHex)
}
func (p *API) BeaconLocalContent(contentKeyHex string) (string, error) {
return p.LocalContent(contentKeyHex)
}
func (p *API) BeaconStore(contentKeyHex string, contextHex string) (bool, error) {
return p.Store(contentKeyHex, contextHex)
}
func (p *API) BeaconGossip(contentKeyHex, contentHex string) (int, error) {
return p.Gossip(contentKeyHex, contentHex)
}
func (p *API) BeaconTraceGetContent(contentKeyHex string) (*portalwire.TraceContentResult, error) {
return p.TraceRecursiveFindContent(contentKeyHex)
}
func NewBeaconNetworkAPI(BeaconAPI *portalwire.PortalProtocolAPI) *API {
return &API{
BeaconAPI,
}
}

View file

@ -1,345 +0,0 @@
package beacon
import (
"bytes"
"context"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
ssz "github.com/ferranbt/fastssz"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/zrnt/eth2/util/merkle"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
const (
LightClientBootstrap storage.ContentType = 0x10
LightClientUpdate storage.ContentType = 0x11
LightClientFinalityUpdate storage.ContentType = 0x12
LightClientOptimisticUpdate storage.ContentType = 0x13
HistoricalSummaries storage.ContentType = 0x14
)
type BeaconNetwork struct {
portalProtocol *portalwire.PortalProtocol
spec *common.Spec
log log.Logger
closeCtx context.Context
closeFunc context.CancelFunc
lightClient *ConsensusLightClient
}
func NewBeaconNetwork(portalProtocol *portalwire.PortalProtocol) *BeaconNetwork {
ctx, cancel := context.WithCancel(context.Background())
return &BeaconNetwork{
portalProtocol: portalProtocol,
spec: configs.Mainnet,
closeCtx: ctx,
closeFunc: cancel,
log: log.New("sub-protocol", "beacon"),
}
}
func (bn *BeaconNetwork) Start() error {
err := bn.portalProtocol.Start()
if err != nil {
return err
}
go bn.processContentLoop(bn.closeCtx)
bn.log.Debug("beacon network start successfully")
return nil
}
func (bn *BeaconNetwork) Stop() {
bn.closeFunc()
bn.portalProtocol.Stop()
}
func (bn *BeaconNetwork) GetUpdates(firstPeriod, count uint64) ([]common.SpecObj, error) {
lightClientUpdateKey := &LightClientUpdateKey{
StartPeriod: firstPeriod,
Count: count,
}
data, err := bn.getContent(LightClientUpdate, lightClientUpdateKey)
if err != nil {
return nil, err
}
var lightClientUpdateRange LightClientUpdateRange = make([]ForkedLightClientUpdate, 0)
err = lightClientUpdateRange.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return nil, err
}
res := make([]common.SpecObj, len(lightClientUpdateRange))
for i, item := range lightClientUpdateRange {
res[i] = item.LightClientUpdate
}
return res, nil
}
func (bn *BeaconNetwork) GetCheckpointData(checkpointHash tree.Root) (common.SpecObj, error) {
bootstrapKey := &LightClientBootstrapKey{
BlockHash: checkpointHash[:],
}
data, err := bn.getContent(LightClientBootstrap, bootstrapKey)
if err != nil {
return nil, err
}
var forkedLightClientBootstrap *ForkedLightClientBootstrap
err = forkedLightClientBootstrap.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return nil, err
}
return forkedLightClientBootstrap.Bootstrap, nil
}
func (bn *BeaconNetwork) GetFinalityUpdate(finalizedSlot uint64) (common.SpecObj, error) {
finalityUpdateKey := &LightClientFinalityUpdateKey{
FinalizedSlot: finalizedSlot,
}
data, err := bn.getContent(LightClientFinalityUpdate, finalityUpdateKey)
if err != nil {
return nil, err
}
var forkedLightClientFinalityUpdate *ForkedLightClientFinalityUpdate
err = forkedLightClientFinalityUpdate.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return nil, err
}
return forkedLightClientFinalityUpdate.LightClientFinalityUpdate, nil
}
func (bn *BeaconNetwork) GetOptimisticUpdate(optimisticSlot uint64) (common.SpecObj, error) {
optimisticUpdateKey := &LightClientOptimisticUpdateKey{
OptimisticSlot: optimisticSlot,
}
data, err := bn.getContent(LightClientOptimisticUpdate, optimisticUpdateKey)
if err != nil {
return nil, err
}
var forkedLightClientOptimisticUpdate *ForkedLightClientOptimisticUpdate
err = forkedLightClientOptimisticUpdate.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return nil, err
}
return forkedLightClientOptimisticUpdate.LightClientOptimisticUpdate, nil
}
func (bn *BeaconNetwork) getContent(contentType storage.ContentType, beaconContentKey ssz.Marshaler) ([]byte, error) {
contentKeyBytes, err := beaconContentKey.MarshalSSZ()
if err != nil {
return nil, err
}
contentKey := storage.NewContentKey(contentType, contentKeyBytes).Encode()
contentId := bn.portalProtocol.ToContentId(contentKey)
res, err := bn.portalProtocol.Get(contentKey, contentId)
// other error
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
return nil, err
}
if res != nil {
return res, nil
}
content, _, err := bn.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
return nil, err
}
return content, nil
}
func (bn *BeaconNetwork) validateContent(contentKey []byte, content []byte) error {
switch storage.ContentType(contentKey[0]) {
case LightClientUpdate:
var lightClientUpdateRange LightClientUpdateRange = make([]ForkedLightClientUpdate, 0)
err := lightClientUpdateRange.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
lightClientUpdateKey := &LightClientUpdateKey{}
err = lightClientUpdateKey.UnmarshalSSZ(contentKey[1:])
if err != nil {
return err
}
if lightClientUpdateKey.Count != uint64(len(lightClientUpdateRange)) {
return fmt.Errorf("light client updates count does not match the content key count: %d != %d", len(lightClientUpdateRange), lightClientUpdateKey.Count)
}
return nil
case LightClientBootstrap:
var forkedLightClientBootstrap ForkedLightClientBootstrap
err := forkedLightClientBootstrap.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
currentSlot := bn.spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(BeaconGenesisTime))
genericBootstrap, err := FromBootstrap(forkedLightClientBootstrap.Bootstrap)
if err != nil {
return err
}
fourMonth := time.Hour * 24 * 30 * 4
fourMonthInSlots := common.Timestamp(fourMonth.Seconds()) / (bn.spec.SECONDS_PER_SLOT)
fourMonthAgoSlot := currentSlot - common.Slot(fourMonthInSlots)
if genericBootstrap.Header.Slot < fourMonthAgoSlot {
return fmt.Errorf("light client bootstrap slot is too old: %d", genericBootstrap.Header.Slot)
}
return nil
case LightClientFinalityUpdate:
lightClientFinalityUpdateKey := &LightClientFinalityUpdateKey{}
err := lightClientFinalityUpdateKey.UnmarshalSSZ(contentKey[1:])
if err != nil {
return err
}
var forkedLightClientFinalityUpdate ForkedLightClientFinalityUpdate
err = forkedLightClientFinalityUpdate.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
if forkedLightClientFinalityUpdate.ForkDigest != Deneb {
return fmt.Errorf("light client finality update is not from the recent fork. Expected deneb, got %v", forkedLightClientFinalityUpdate.ForkDigest)
}
finalizedSlot := lightClientFinalityUpdateKey.FinalizedSlot
genericUpdate, err := FromLightClientFinalityUpdate(forkedLightClientFinalityUpdate.LightClientFinalityUpdate)
if err != nil {
return err
}
if finalizedSlot != uint64(genericUpdate.FinalizedHeader.Slot) {
return fmt.Errorf("light client finality update finalized slot does not match the content key finalized slot: %d != %d", genericUpdate.FinalizedHeader.Slot, finalizedSlot)
}
return nil
case LightClientOptimisticUpdate:
lightClientOptimisticUpdateKey := &LightClientOptimisticUpdateKey{}
err := lightClientOptimisticUpdateKey.UnmarshalSSZ(contentKey[1:])
if err != nil {
return err
}
var forkedLightClientOptimisticUpdate ForkedLightClientOptimisticUpdate
err = forkedLightClientOptimisticUpdate.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
if forkedLightClientOptimisticUpdate.ForkDigest != Deneb {
return fmt.Errorf("light client optimistic update is not from the recent fork. Expected deneb, got %v", forkedLightClientOptimisticUpdate.ForkDigest)
}
genericUpdate, err := FromLightClientOptimisticUpdate(forkedLightClientOptimisticUpdate.LightClientOptimisticUpdate)
if err != nil {
return err
}
// Check if key signature slot matches the light client optimistic update signature slot
if lightClientOptimisticUpdateKey.OptimisticSlot != uint64(genericUpdate.SignatureSlot) {
return fmt.Errorf("light client optimistic update signature slot does not match the content key signature slot: %d != %d", genericUpdate.SignatureSlot, lightClientOptimisticUpdateKey.OptimisticSlot)
}
return nil
// TODO: VERIFY
case HistoricalSummaries:
forkedHistoricalSummariesWithProof, err := bn.generalSummariesValidation(contentKey, content)
if err != nil {
return err
}
// TODO get root from light client
header := bn.lightClient.GetFinalityHeader()
latestFinalizedRoot := header.StateRoot
valid := bn.stateSummariesValidation(*forkedHistoricalSummariesWithProof, latestFinalizedRoot)
if !valid {
return errors.New("merkle proof validation failed for HistoricalSummariesProof")
}
return nil
default:
return fmt.Errorf("unknown content type %v", contentKey[0])
}
}
func (bn *BeaconNetwork) validateContents(contentKeys [][]byte, contents [][]byte) error {
for i, content := range contents {
contentKey := contentKeys[i]
err := bn.validateContent(contentKey, content)
if err != nil {
bn.log.Error("content validate failed", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content), "err", err)
return fmt.Errorf("content validate failed with content key %x and content %x", contentKey, content)
}
contentId := bn.portalProtocol.ToContentId(contentKey)
err = bn.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
bn.log.Error("put content failed", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content), "err", err)
return err
}
}
return nil
}
func (bn *BeaconNetwork) processContentLoop(ctx context.Context) {
contentChan := bn.portalProtocol.GetContent()
for {
select {
case <-ctx.Done():
return
case contentElement := <-contentChan:
err := bn.validateContents(contentElement.ContentKeys, contentElement.Contents)
if err != nil {
bn.log.Error("validate content failed", "err", err)
continue
}
go func(ctx context.Context) {
select {
case <-ctx.Done():
return
default:
var gossippedNum int
gossippedNum, err = bn.portalProtocol.Gossip(&contentElement.Node, contentElement.ContentKeys, contentElement.Contents)
bn.log.Trace("gossippedNum", "gossippedNum", gossippedNum)
if err != nil {
bn.log.Error("gossip failed", "err", err)
return
}
}
}(ctx)
}
}
}
func (bn *BeaconNetwork) generalSummariesValidation(contentKey, content []byte) (*ForkedHistoricalSummariesWithProof, error) {
key := &HistoricalSummariesWithProofKey{}
err := key.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey[1:]), uint64(len(contentKey[1:]))))
if err != nil {
return nil, err
}
forkedHistoricalSummariesWithProof := &ForkedHistoricalSummariesWithProof{}
err = forkedHistoricalSummariesWithProof.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return nil, err
}
if forkedHistoricalSummariesWithProof.HistoricalSummariesWithProof.EPOCH != common.Epoch(key.Epoch) {
return nil, fmt.Errorf("historical summaries with proof epoch does not match the content key epoch: %d != %d", forkedHistoricalSummariesWithProof.HistoricalSummariesWithProof.EPOCH, key.Epoch)
}
return forkedHistoricalSummariesWithProof, nil
}
func (bn *BeaconNetwork) stateSummariesValidation(f ForkedHistoricalSummariesWithProof, latestFinalizedRoot common.Root) bool {
proof := f.HistoricalSummariesWithProof.Proof
summariesRoot := f.HistoricalSummariesWithProof.HistoricalSummaries.HashTreeRoot(bn.spec, tree.GetHashFn())
gIndex := 59
return merkle.VerifyMerkleBranch(summariesRoot, proof.Proof[:], 5, uint64(gIndex), latestFinalizedRoot)
}

View file

@ -1,105 +0,0 @@
package beacon
import (
"bytes"
"testing"
"github.com/protolambda/ztyp/codec"
"github.com/stretchr/testify/require"
)
func TestLightClientBootstrapValidation(t *testing.T) {
bootstrap, err := GetLightClientBootstrap(0)
require.NoError(t, err)
contentKey := make([]byte, 33)
contentKey[0] = byte(LightClientBootstrap)
bn := NewBeaconNetwork(nil)
var buf bytes.Buffer
bootstrap.Serialize(bn.spec, codec.NewEncodingWriter(&buf))
err = bn.validateContent(contentKey, buf.Bytes())
require.NoError(t, err)
}
func TestLightClienUpdateValidation(t *testing.T) {
update, err := GetClientUpdate(0)
require.NoError(t, err)
key := &LightClientUpdateKey{
StartPeriod: 0,
Count: 1,
}
updateRange := LightClientUpdateRange([]ForkedLightClientUpdate{update})
keyData, err := key.MarshalSSZ()
require.NoError(t, err)
contentKey := make([]byte, 0)
contentKey = append(contentKey, byte(LightClientUpdate))
contentKey = append(contentKey, keyData...)
bn := NewBeaconNetwork(nil)
var buf bytes.Buffer
updateRange.Serialize(bn.spec, codec.NewEncodingWriter(&buf))
err = bn.validateContent(contentKey, buf.Bytes())
require.NoError(t, err)
}
func TestLightClientFinalityUpdateValidation(t *testing.T) {
update, err := GetLightClientFinalityUpdate(0)
require.NoError(t, err)
key := &LightClientFinalityUpdateKey{
FinalizedSlot: 10934316269310501102,
}
keyData, err := key.MarshalSSZ()
require.NoError(t, err)
contentKey := make([]byte, 0)
contentKey = append(contentKey, byte(LightClientFinalityUpdate))
contentKey = append(contentKey, keyData...)
bn := NewBeaconNetwork(nil)
var buf bytes.Buffer
update.Serialize(bn.spec, codec.NewEncodingWriter(&buf))
err = bn.validateContent(contentKey, buf.Bytes())
require.NoError(t, err)
}
func TestLightClientOptimisticUpdateValidation(t *testing.T) {
update, err := GetLightClientOptimisticUpdate(0)
require.NoError(t, err)
key := &LightClientOptimisticUpdateKey{
OptimisticSlot: 15067541596220156845,
}
keyData, err := key.MarshalSSZ()
require.NoError(t, err)
contentKey := make([]byte, 0)
contentKey = append(contentKey, byte(LightClientOptimisticUpdate))
contentKey = append(contentKey, keyData...)
bn := NewBeaconNetwork(nil)
var buf bytes.Buffer
update.Serialize(bn.spec, codec.NewEncodingWriter(&buf))
err = bn.validateContent(contentKey, buf.Bytes())
require.NoError(t, err)
}
func TestHistorySummariesWithProofValidation(t *testing.T) {
historySummariesWithProof, root, err := GetHistorySummariesWithProof()
require.NoError(t, err)
key := &HistoricalSummariesWithProofKey{
Epoch: 450508969718611630,
}
var keyBuf bytes.Buffer
err = key.Serialize(codec.NewEncodingWriter(&keyBuf))
require.NoError(t, err)
contentKey := make([]byte, 0)
contentKey = append(contentKey, byte(HistoricalSummaries))
contentKey = append(contentKey, keyBuf.Bytes()...)
bn := NewBeaconNetwork(nil)
var buf bytes.Buffer
err = historySummariesWithProof.Serialize(bn.spec, codec.NewEncodingWriter(&buf))
require.NoError(t, err)
content := make([]byte, 0)
content = append(content, Deneb[:]...)
content = append(content, buf.Bytes()...)
forkedHistorySummaries, err := bn.generalSummariesValidation(contentKey, content)
require.NoError(t, err)
valid := bn.stateSummariesValidation(*forkedHistorySummaries, root)
require.True(t, valid)
}

View file

@ -1,703 +0,0 @@
package beacon
import (
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/protolambda/zrnt/eth2/beacon/altair"
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/zrnt/eth2/util/merkle"
"github.com/ethereum/go-ethereum/common/hexutil"
blsu "github.com/protolambda/bls12-381-util"
"github.com/protolambda/ztyp/tree"
"github.com/protolambda/ztyp/view"
)
var (
ErrInsufficientParticipation = errors.New("insufficient participation")
ErrInvalidTimestamp = errors.New("invalid timestamp")
ErrInvalidPeriod = errors.New("invalid sync committee period")
ErrNotRelevant = errors.New("update not relevant")
ErrInvalidFinalityProof = errors.New("invalid finality proof")
ErrInvalidNextSyncCommitteeProof = errors.New("invalid next sync committee proof")
ErrInvalidSignature = errors.New("invalid sync committee signature")
)
type ConsensusAPI interface {
GetBootstrap(blockRoot common.Root) (common.SpecObj, error)
GetUpdates(firstPeriod, count uint64) ([]common.SpecObj, error)
GetFinalityUpdate() (common.SpecObj, error)
GetOptimisticUpdate() (common.SpecObj, error)
ChainID() uint64
Name() string
}
type LightClientStore struct {
FinalizedHeader *common.BeaconBlockHeader
CurrentSyncCommittee *common.SyncCommittee
NextSyncCommittee *common.SyncCommittee
OptimisticHeader *common.BeaconBlockHeader
PreviousMaxActiveParticipants view.Uint64View
CurrentMaxActiveParticipants view.Uint64View
}
type ConsensusLightClient struct {
Store LightClientStore
API ConsensusAPI
InitialCheckpoint common.Root
LastCheckpoint common.Root
Config *Config
Logger log.Logger
}
type Config struct {
ConsensusAPI string
Port uint64
DefaultCheckpoint common.Root
Checkpoint common.Root
DataDir string
Chain ChainConfig
Spec *common.Spec
MaxCheckpointAge uint64
Fallback string
LoadExternalFallback bool
StrictCheckpointAge bool
}
type ChainConfig struct {
ChainID uint64
GenesisTime uint64
GenesisRoot common.Root
}
type GenericUpdate struct {
AttestedHeader *common.BeaconBlockHeader
SyncAggregate *altair.SyncAggregate
SignatureSlot common.Slot
NextSyncCommittee *common.SyncCommittee
NextSyncCommitteeBranch *altair.SyncCommitteeProofBranch
FinalizedHeader *common.BeaconBlockHeader
FinalityBranch *altair.FinalizedRootProofBranch
}
type GenericBootstrap struct {
Header *common.BeaconBlockHeader
CurrentSyncCommittee common.SyncCommittee
CurrentSyncCommitteeBranch altair.SyncCommitteeProofBranch
}
func FromBootstrap(commonBootstrap common.SpecObj) (*GenericBootstrap, error) {
switch bootstrap := commonBootstrap.(type) {
case *deneb.LightClientBootstrap:
return &GenericBootstrap{
Header: &bootstrap.Header.Beacon,
CurrentSyncCommittee: bootstrap.CurrentSyncCommittee,
CurrentSyncCommitteeBranch: bootstrap.CurrentSyncCommitteeBranch,
}, nil
case *capella.LightClientBootstrap:
return &GenericBootstrap{
Header: &bootstrap.Header.Beacon,
CurrentSyncCommittee: bootstrap.CurrentSyncCommittee,
CurrentSyncCommitteeBranch: bootstrap.CurrentSyncCommitteeBranch,
}, nil
case *altair.LightClientBootstrap:
return &GenericBootstrap{
Header: &bootstrap.Header.Beacon,
CurrentSyncCommittee: bootstrap.CurrentSyncCommittee,
CurrentSyncCommitteeBranch: bootstrap.CurrentSyncCommitteeBranch,
}, nil
}
return nil, errors.New("unknown bootstrap type")
}
func NewConsensusLightClient(api ConsensusAPI, config *Config, checkpointBlockRoot common.Root, logger log.Logger) (*ConsensusLightClient, error) {
client := &ConsensusLightClient{
API: api,
Config: config,
Logger: logger,
InitialCheckpoint: checkpointBlockRoot,
}
err := client.bootstrap()
if err != nil {
return nil, err
}
return client, nil
}
func (c *ConsensusLightClient) GetHeader() *common.BeaconBlockHeader {
return c.Store.OptimisticHeader
}
func (c *ConsensusLightClient) GetFinalityHeader() *common.BeaconBlockHeader {
return c.Store.FinalizedHeader
}
func (c *ConsensusLightClient) Sync() error {
err := c.bootstrap()
if err != nil {
return err
}
bootstrapPeriod := CalcSyncPeriod(uint64(c.Store.FinalizedHeader.Slot))
updates := make([]common.SpecObj, 0)
if c.API.Name() == "portal" {
currentPeriod := CalcSyncPeriod(uint64(c.expectedCurrentSlot()))
for i := bootstrapPeriod; i < currentPeriod; i++ {
update, err := c.API.GetUpdates(i, 1)
if err != nil {
return err
}
updates = append(updates, update...)
}
} else {
updates, err = c.API.GetUpdates(bootstrapPeriod, MaxRequestLightClientUpdates)
if err != nil {
return err
}
}
for _, update := range updates {
err = c.VerifyUpdate(update)
if err != nil {
return err
}
c.ApplyUpdate(update)
}
finalityUpdate, err := c.API.GetFinalityUpdate()
if err != nil {
return err
}
err = c.VerifyFinalityUpdate(finalityUpdate)
if err != nil {
return err
}
c.ApplyFinalityUpdate(finalityUpdate)
optimisticUpdate, err := c.API.GetOptimisticUpdate()
if err != nil {
return err
}
err = c.VerifyOptimisticUpdate(optimisticUpdate)
if err != nil {
return err
}
c.ApplyOptimisticUpdate(optimisticUpdate)
c.Logger.Info("Light client in sync with ", "checkpoint", hexutil.Encode(c.InitialCheckpoint[:]))
return nil
}
func (c *ConsensusLightClient) Advance() error {
finalityUpdate, err := c.API.GetFinalityUpdate()
if err != nil {
return err
}
err = c.VerifyFinalityUpdate(finalityUpdate)
if err != nil {
return err
}
c.ApplyFinalityUpdate(finalityUpdate)
optimisticUpdate, err := c.API.GetOptimisticUpdate()
if err != nil {
return err
}
err = c.VerifyOptimisticUpdate(optimisticUpdate)
if err != nil {
return err
}
c.ApplyOptimisticUpdate(optimisticUpdate)
if c.Store.NextSyncCommittee == nil {
c.Logger.Debug("checking for sync committee update")
currentPeriod := CalcSyncPeriod(uint64(c.Store.FinalizedHeader.Slot))
updates, err := c.API.GetUpdates(currentPeriod, 1)
if err != nil {
return err
}
if len(updates) == 1 {
update := updates[0]
err = c.VerifyUpdate(update)
if err != nil {
return err
}
c.Logger.Info("updating sync committee")
c.ApplyUpdate(update)
}
}
return nil
}
func (c *ConsensusLightClient) bootstrap() error {
forkedBootstrap, err := c.API.GetBootstrap(c.InitialCheckpoint)
if err != nil {
return err
}
bootstrap, err := FromBootstrap(forkedBootstrap)
if err != nil {
return err
}
isValid := c.isValidCheckpoint(bootstrap.Header.Slot)
if !isValid {
if c.Config.StrictCheckpointAge {
return errors.New("checkpoint is too old")
} else {
c.Logger.Warn("checkpoint is too old")
}
}
committeeValid := c.isCurrentCommitteeProofValid(*bootstrap.Header, bootstrap.CurrentSyncCommittee, bootstrap.CurrentSyncCommitteeBranch)
headerHash := bootstrap.Header.HashTreeRoot(tree.GetHashFn()).String()
expectedHash := c.InitialCheckpoint.String()
headerValid := headerHash == expectedHash
if !headerValid {
return fmt.Errorf("header hash %s does not match expected hash %s", headerHash, expectedHash)
}
if !committeeValid {
return errors.New("committee proof is invalid")
}
c.Store = LightClientStore{
FinalizedHeader: bootstrap.Header,
CurrentSyncCommittee: &bootstrap.CurrentSyncCommittee,
OptimisticHeader: bootstrap.Header,
PreviousMaxActiveParticipants: view.Uint64View(0),
CurrentMaxActiveParticipants: view.Uint64View(0),
}
return nil
}
func (c *ConsensusLightClient) isValidCheckpoint(blockHashSlot common.Slot) bool {
currentSlot := c.expectedCurrentSlot()
currentSlotTimestamp, err := c.slotTimestamp(currentSlot)
if err != nil {
return false
}
blockHashSlotTimestamp, err := c.slotTimestamp(blockHashSlot)
if err != nil {
return false
}
slotAge := currentSlotTimestamp - blockHashSlotTimestamp
return uint64(slotAge) < c.Config.MaxCheckpointAge
}
func (c *ConsensusLightClient) VerifyGenericUpdate(update *GenericUpdate) error {
bits := c.getBits(update.SyncAggregate.SyncCommitteeBits)
if bits == 0 {
return ErrInsufficientParticipation
}
updateFinalizedSlot := common.Slot(0)
if update.FinalizedHeader != nil {
updateFinalizedSlot = update.FinalizedHeader.Slot
}
validTime := uint64(c.expectedCurrentSlot()) >= uint64(update.SignatureSlot) && update.SignatureSlot > update.AttestedHeader.Slot && update.AttestedHeader.Slot >= updateFinalizedSlot
if !validTime {
return ErrInvalidTimestamp
}
storePeriod := CalcSyncPeriod(uint64(c.Store.FinalizedHeader.Slot))
updateSigPeriod := CalcSyncPeriod(uint64(update.SignatureSlot))
validPeriod := false
if c.Store.NextSyncCommittee != nil {
validPeriod = updateSigPeriod == storePeriod || updateSigPeriod == storePeriod+1
} else {
validPeriod = updateSigPeriod == storePeriod
}
if !validPeriod {
return ErrInvalidPeriod
}
updateAttestedPeriod := CalcSyncPeriod(uint64(update.AttestedHeader.Slot))
updateHasNextCommittee := c.Store.NextSyncCommittee == nil && update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod
if update.AttestedHeader.Slot <= c.Store.FinalizedHeader.Slot && !updateHasNextCommittee {
return ErrNotRelevant
}
if update.FinalizedHeader != nil && update.FinalityBranch != nil {
isValid := IsFinalityProofValid(*update.AttestedHeader, *update.FinalizedHeader, *update.FinalityBranch)
if !isValid {
return ErrInvalidFinalityProof
}
}
if update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil {
isValid := IsNextCommitteeProofValid(*update.AttestedHeader, *update.NextSyncCommittee, *update.NextSyncCommitteeBranch)
if !isValid {
return ErrInvalidNextSyncCommitteeProof
}
}
var syncCommittee *common.SyncCommittee
if updateSigPeriod == storePeriod {
syncCommittee = c.Store.CurrentSyncCommittee
} else {
syncCommittee = c.Store.NextSyncCommittee
}
pks := c.getParticipatingKeys(*syncCommittee, update.SyncAggregate.SyncCommitteeBits)
isValidSig, err := c.VerifySyncCommitteeSignature(pks, *update.AttestedHeader, update.SyncAggregate.SyncCommitteeSignature, update.SignatureSlot)
if err != nil {
return err
}
if !isValidSig {
return ErrInvalidSignature
}
return nil
}
func (c *ConsensusLightClient) VerifyUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientUpdate(update)
if err != nil {
return err
}
return c.VerifyGenericUpdate(genericUpdate)
}
func (c *ConsensusLightClient) VerifyFinalityUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientFinalityUpdate(update)
if err != nil {
return err
}
return c.VerifyGenericUpdate(genericUpdate)
}
func (c *ConsensusLightClient) VerifyOptimisticUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientOptimisticUpdate(update)
if err != nil {
return err
}
return c.VerifyGenericUpdate(genericUpdate)
}
func (c *ConsensusLightClient) ApplyGenericUpdate(update *GenericUpdate) {
commiteeBits := c.getBits(update.SyncAggregate.SyncCommitteeBits)
if c.Store.CurrentMaxActiveParticipants < view.Uint64View(commiteeBits) {
c.Store.CurrentMaxActiveParticipants = view.Uint64View(commiteeBits)
}
shouldUpdateOptimistic := commiteeBits > c.safetyThreshold() && update.AttestedHeader.Slot > c.Store.OptimisticHeader.Slot
if shouldUpdateOptimistic {
c.Store.OptimisticHeader = update.AttestedHeader
c.logFinalityUpdate(update)
}
updateAttestedPeriod := CalcSyncPeriod(uint64(update.AttestedHeader.Slot))
updateFinalizedSlot := common.Slot(0)
if update.FinalizedHeader != nil {
updateFinalizedSlot = update.FinalizedHeader.Slot
}
updateFinalizedPeriod := CalcSyncPeriod(uint64(updateFinalizedSlot))
updateHasFinalizedNextCommittee := c.Store.NextSyncCommittee == nil &&
c.hasSyncUpdate(update) &&
c.hasFinalityUpdate(update) &&
updateFinalizedPeriod == updateAttestedPeriod
hasMajority := commiteeBits*3 >= 512*2
updateIsNewer := updateFinalizedSlot > c.Store.FinalizedHeader.Slot
goodUpdate := updateIsNewer || updateHasFinalizedNextCommittee
shouldApplyUpdate := hasMajority && goodUpdate
if shouldApplyUpdate {
storePeriod := CalcSyncPeriod(uint64(c.Store.FinalizedHeader.Slot))
if c.Store.NextSyncCommittee == nil {
c.Store.NextSyncCommittee = update.NextSyncCommittee
} else if updateFinalizedPeriod == storePeriod+1 {
c.Logger.Info("sync committee updated")
c.Store.CurrentSyncCommittee = c.Store.NextSyncCommittee
c.Store.NextSyncCommittee = update.NextSyncCommittee
c.Store.PreviousMaxActiveParticipants = c.Store.CurrentMaxActiveParticipants
c.Store.CurrentMaxActiveParticipants = 0
}
if updateFinalizedSlot > c.Store.FinalizedHeader.Slot {
c.Store.FinalizedHeader = update.FinalizedHeader
c.logFinalityUpdate(update)
if c.Store.FinalizedHeader.Slot%32 == 0 {
checkpoint := c.Store.FinalizedHeader.HashTreeRoot(tree.GetHashFn())
c.LastCheckpoint = checkpoint
}
if c.Store.FinalizedHeader.Slot > c.Store.OptimisticHeader.Slot {
c.Store.OptimisticHeader = c.Store.FinalizedHeader
}
}
}
}
func (c *ConsensusLightClient) ApplyUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientUpdate(update)
if err != nil {
return err
}
c.ApplyGenericUpdate(genericUpdate)
return nil
}
func (c *ConsensusLightClient) ApplyFinalityUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientFinalityUpdate(update)
if err != nil {
return err
}
c.ApplyGenericUpdate(genericUpdate)
return nil
}
func (c *ConsensusLightClient) ApplyOptimisticUpdate(update common.SpecObj) error {
genericUpdate, err := FromLightClientOptimisticUpdate(update)
if err != nil {
return err
}
c.ApplyGenericUpdate(genericUpdate)
return nil
}
func (c *ConsensusLightClient) VerifySyncCommitteeSignature(pks []common.BLSPubkey, attestedHeader common.BeaconBlockHeader, signature common.BLSSignature, signatureSlot common.Slot) (bool, error) {
headerRoot := attestedHeader.HashTreeRoot(tree.GetHashFn())
signingRoot := c.ComputeCommitteeSignRoot(headerRoot, signatureSlot)
blsuPubKeys := make([]*blsu.Pubkey, 0, len(pks))
for _, p := range pks {
blsuPubKey, err := p.Pubkey()
if err != nil {
return false, err
}
blsuPubKeys = append(blsuPubKeys, blsuPubKey)
}
blsuSig, err := signature.Signature()
if err != nil {
return false, err
}
return blsu.FastAggregateVerify(blsuPubKeys, signingRoot[:], blsuSig), nil
}
func (c *ConsensusLightClient) ComputeCommitteeSignRoot(headerRoot tree.Root, slot common.Slot) common.Root {
genesisRoot := c.Config.Chain.GenesisRoot
domainType := hexutil.MustDecode("0x07000000")
forkVersion := c.Config.Spec.ForkVersion(slot)
domain := common.ComputeDomain(common.BLSDomainType(domainType), forkVersion, genesisRoot)
return ComputeSigningRoot(headerRoot, domain)
}
func (c *ConsensusLightClient) expectedCurrentSlot() common.Slot {
return c.Config.Spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(c.Config.Chain.GenesisTime))
}
func (c *ConsensusLightClient) slotTimestamp(slot common.Slot) (common.Timestamp, error) {
atSlot, err := c.Config.Spec.TimeAtSlot(slot, common.Timestamp(c.Config.Chain.GenesisTime))
if err != nil {
return 0, err
}
return atSlot, nil
}
func (c *ConsensusLightClient) isCurrentCommitteeProofValid(attestedHeader common.BeaconBlockHeader, currentCommittee common.SyncCommittee, currentCommitteeBranch altair.SyncCommitteeProofBranch) bool {
return merkle.VerifyMerkleBranch(currentCommittee.HashTreeRoot(c.Config.Spec, tree.GetHashFn()), currentCommitteeBranch[:], 5, 22, attestedHeader.StateRoot)
}
func (c *ConsensusLightClient) safetyThreshold() uint64 {
if c.Store.CurrentMaxActiveParticipants > c.Store.PreviousMaxActiveParticipants {
return uint64(c.Store.CurrentMaxActiveParticipants) / 2
} else {
return uint64(c.Store.PreviousMaxActiveParticipants) / 2
}
}
func (c *ConsensusLightClient) hasSyncUpdate(update *GenericUpdate) bool {
return update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil
}
func (c *ConsensusLightClient) hasFinalityUpdate(update *GenericUpdate) bool {
return update.FinalizedHeader != nil && update.FinalityBranch != nil
}
func (c *ConsensusLightClient) logFinalityUpdate(update *GenericUpdate) {
count := c.getBits(update.SyncAggregate.SyncCommitteeBits)
participation := float32(count) / 512 * 100
decimals := 0
if participation == 100.0 {
decimals = 1
} else {
decimals = 2
}
slot := c.Store.OptimisticHeader.Slot
age, err := c.age(slot)
if err != nil {
c.Logger.Error("failed to get age", "slot is", slot, "err is", err)
return
}
days := int(age.Hours() / 24)
hours := int(age.Hours()) % 24
minutes := int(age.Minutes()) % 60
secs := int(age.Seconds()) % 60
ageStr := fmt.Sprintf("%d:%d:%d:%d", days, hours, minutes, secs)
c.Logger.Info("update header", "slot=", slot, "confidence=", decimals, "age", ageStr)
}
func (c *ConsensusLightClient) age(slot common.Slot) (time.Duration, error) {
expectTime, err := c.slotTimestamp(slot)
if err != nil {
return time.Duration(0), err
}
return time.Since(time.Unix(int64(expectTime), 0)), nil
}
func (c *ConsensusLightClient) getBits(sync altair.SyncCommitteeBits) uint64 {
res := 0
for i := 0; i < int(c.Config.Spec.SYNC_COMMITTEE_SIZE); i++ {
if sync.GetBit(uint64(i)) {
res++
}
}
return uint64(res)
}
func (c *ConsensusLightClient) getParticipatingKeys(committee common.SyncCommittee, syncBits altair.SyncCommitteeBits) []common.BLSPubkey {
res := make([]common.BLSPubkey, 0)
for i := 0; i < int(c.Config.Spec.SYNC_COMMITTEE_SIZE); i++ {
if syncBits.GetBit(uint64(i)) {
res = append(res, committee.Pubkeys[i])
}
}
return res
}
func FromLightClientUpdate(commonUpdate common.SpecObj) (*GenericUpdate, error) {
switch update := commonUpdate.(type) {
case *deneb.LightClientUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
NextSyncCommittee: &update.NextSyncCommittee,
NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}, nil
case *capella.LightClientUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
NextSyncCommittee: &update.NextSyncCommittee,
NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}, nil
case *altair.LightClientUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
NextSyncCommittee: &update.NextSyncCommittee,
NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}, nil
}
return nil, errors.New("unknown update type")
}
func FromLightClientFinalityUpdate(commonFinalityUpdate common.SpecObj) (*GenericUpdate, error) {
switch update := commonFinalityUpdate.(type) {
case *deneb.LightClientFinalityUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}, nil
case *capella.LightClientFinalityUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}, nil
case *altair.LightClientFinalityUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
FinalizedHeader: &update.FinalizedHeader,
FinalityBranch: &update.FinalityBranch,
}, nil
}
return nil, errors.New("unknown finality update type")
}
func FromLightClientOptimisticUpdate(commonOptimisticUpdate common.SpecObj) (*GenericUpdate, error) {
switch update := commonOptimisticUpdate.(type) {
case *deneb.LightClientOptimisticUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
}, nil
case *capella.LightClientOptimisticUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
}, nil
case *altair.LightClientOptimisticUpdate:
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
}, nil
}
return nil, errors.New("unknown optimistic update type")
}
func ComputeSigningRoot(root common.Root, domain common.BLSDomain) common.Root {
data := common.SigningData{
ObjectRoot: root,
Domain: domain,
}
return data.HashTreeRoot(tree.GetHashFn())
}
func CalcSyncPeriod(slot uint64) uint64 {
epoch := slot / 32 // 32 slots per epoch
return epoch / 256 // 256 epochs per sync committee
}
func IsFinalityProofValid(attestedHeader common.BeaconBlockHeader, finalityHeader common.BeaconBlockHeader, finalityBranch altair.FinalizedRootProofBranch) bool {
leaf := finalityHeader.HashTreeRoot(tree.GetHashFn())
root := attestedHeader.StateRoot
return merkle.VerifyMerkleBranch(leaf, finalityBranch[:], 6, 41, root)
}
func IsNextCommitteeProofValid(attestedHeader common.BeaconBlockHeader, nextCommittee common.SyncCommittee, nextCommitteeBranch altair.SyncCommitteeProofBranch) bool {
leaf := nextCommittee.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
root := attestedHeader.StateRoot
return merkle.VerifyMerkleBranch(leaf, nextCommitteeBranch[:], 5, 23, root)
}

View file

@ -1,198 +0,0 @@
package beacon
import (
"encoding/json"
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var _ ConsensusAPI = (*MockConsensusAPI)(nil)
type MockConsensusAPI struct {
testdataDir string
}
func NewMockConsensusAPI(path string) (ConsensusAPI, error) {
return &MockConsensusAPI{testdataDir: path}, nil
}
func (m MockConsensusAPI) GetUpdates(_, _ uint64) ([]common.SpecObj, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/updates.json")
updates := make([]*capella.LightClientUpdate, 0)
_ = json.Unmarshal(jsonStr, &updates)
res := make([]common.SpecObj, 0)
for _, item := range updates {
res = append(res, item)
}
return res, nil
}
func (m MockConsensusAPI) GetBootstrap(_ common.Root) (common.SpecObj, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/bootstrap.json")
bootstrap := &capella.LightClientBootstrap{}
_ = json.Unmarshal(jsonStr, &bootstrap)
return bootstrap, nil
}
func (m MockConsensusAPI) GetFinalityUpdate() (common.SpecObj, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/finality.json")
finality := &capella.LightClientFinalityUpdate{}
_ = json.Unmarshal(jsonStr, &finality)
return finality, nil
}
func (m MockConsensusAPI) GetOptimisticUpdate() (common.SpecObj, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/optimistic.json")
optimistic := &capella.LightClientOptimisticUpdate{}
_ = json.Unmarshal(jsonStr, &optimistic)
return optimistic, nil
}
func (m MockConsensusAPI) ChainID() uint64 {
panic("implement me")
}
func (m MockConsensusAPI) Name() string {
return "mock"
}
func getClient(strictCheckpointAge bool, t *testing.T) (*ConsensusLightClient, error) {
baseConfig := Mainnet()
api, err := NewMockConsensusAPI("testdata/mockdata")
assert.NoError(t, err)
config := &Config{
ConsensusAPI: api.Name(),
Chain: baseConfig.Chain,
Spec: baseConfig.Spec,
StrictCheckpointAge: strictCheckpointAge,
}
checkpoint := common.Root(hexutil.MustDecode("0xc62aa0de55e6f21230fa63713715e1a6c13e73005e89f6389da271955d819bde"))
client, err := NewConsensusLightClient(api, config, checkpoint, testlog.Logger(t, log.LvlTrace))
if err != nil {
return nil, err
}
return client, nil
}
func TestVerifyCheckpointAgeInvalid(t *testing.T) {
_, err := getClient(true, t)
assert.ErrorContains(t, err, "checkpoint is too old")
}
func TestVerifyUpdate(t *testing.T) {
client, err := getClient(false, t)
require.NoError(t, err)
client.Config.MaxCheckpointAge = 123123123
period := CalcSyncPeriod(uint64(client.Store.FinalizedHeader.Slot))
updates, err := client.API.GetUpdates(period, MaxRequestLightClientUpdates)
require.NoError(t, err)
// normal
err = client.VerifyUpdate(updates[0])
require.NoError(t, err)
// ErrInvalidNextSyncCommitteeProof
genericUpdate, err := FromLightClientUpdate(updates[0])
require.NoError(t, err)
genericUpdate.NextSyncCommittee.Pubkeys[0] = common.BLSPubkey{}
err = client.VerifyGenericUpdate(genericUpdate)
require.Equal(t, ErrInvalidNextSyncCommitteeProof, err)
// ErrInvalidFinalityProof
updates, err = client.API.GetUpdates(period, MaxRequestLightClientUpdates)
require.NoError(t, err)
genericUpdate, err = FromLightClientUpdate(updates[0])
require.NoError(t, err)
genericUpdate.FinalizedHeader = &common.BeaconBlockHeader{}
err = client.VerifyGenericUpdate(genericUpdate)
require.Equal(t, ErrInvalidFinalityProof, err)
// ErrInvalidSignature
updates, err = client.API.GetUpdates(period, MaxRequestLightClientUpdates)
require.NoError(t, err)
genericUpdate, err = FromLightClientUpdate(updates[0])
require.NoError(t, err)
genericUpdate.SyncAggregate.SyncCommitteeSignature[1] = 0xFE
err = client.VerifyGenericUpdate(genericUpdate)
require.Error(t, err)
}
func TestVerifyFinalityUpdate(t *testing.T) {
client, err := getClient(false, t)
require.NoError(t, err)
update, err := client.API.GetFinalityUpdate()
require.NoError(t, err)
// normal
err = client.VerifyFinalityUpdate(update)
require.NoError(t, err)
genericUpdate, err := FromLightClientFinalityUpdate(update)
require.NoError(t, err)
genericUpdate.FinalizedHeader = &common.BeaconBlockHeader{}
err = client.VerifyGenericUpdate(genericUpdate)
require.Equal(t, ErrInvalidFinalityProof, err)
// ErrInvalidSignature
update, err = client.API.GetFinalityUpdate()
require.NoError(t, err)
genericUpdate, err = FromLightClientFinalityUpdate(update)
require.NoError(t, err)
genericUpdate.SyncAggregate.SyncCommitteeSignature[1] = 0xFE
err = client.VerifyGenericUpdate(genericUpdate)
require.Error(t, err)
}
func TestVerifyOptimisticUpdate(t *testing.T) {
client, err := getClient(false, t)
require.NoError(t, err)
update, err := client.API.GetOptimisticUpdate()
require.NoError(t, err)
// normal
err = client.VerifyOptimisticUpdate(update)
require.NoError(t, err)
genericUpdate, err := FromLightClientOptimisticUpdate(update)
require.NoError(t, err)
genericUpdate.SyncAggregate.SyncCommitteeSignature = common.BLSSignature{}
err = client.VerifyGenericUpdate(genericUpdate)
require.Error(t, err)
}
func TestSync(t *testing.T) {
client, err := getClient(false, t)
require.NoError(t, err)
err = client.Sync()
require.NoError(t, err)
header := client.GetHeader()
require.Equal(t, header.Slot, common.Slot(7358726))
finalizedHead := client.GetFinalityHeader()
require.Equal(t, finalizedHead.Slot, common.Slot(7358656))
}

View file

@ -1,44 +0,0 @@
package beacon
import (
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/configs"
)
const MainnetType = iota
type BaseConfig struct {
APIPort uint64
API string
DefaultCheckpoint common.Root
Chain ChainConfig
Spec *common.Spec
MaxCheckpointAge uint64
}
func Mainnet() *BaseConfig {
return &BaseConfig{
APIPort: 8545,
API: "https://www.lightclientdata.org",
DefaultCheckpoint: common.Root(hexutil.MustDecode("0x766647f3c4e1fc91c0db9a9374032ae038778411fbff222974e11f2e3ce7dadf")),
Chain: ChainConfig{
ChainID: 1,
GenesisTime: 1606824023,
GenesisRoot: common.Root(hexutil.MustDecode("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95")),
},
Spec: configs.Mainnet,
MaxCheckpointAge: 1_209_600,
}
}
func ToBaseConfig(networkType int) (*BaseConfig, error) {
switch networkType {
case MainnetType:
return Mainnet(), nil
default:
return nil, errors.New("unknown network type")
}
}

View file

@ -1,158 +0,0 @@
package beacon
import (
"bytes"
"errors"
"time"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
const BeaconGenesisTime uint64 = 1606824023
var _ ConsensusAPI = &PortalLightApi{}
type PortalLightApi struct {
portalProtocol *portalwire.PortalProtocol
spec *common.Spec
}
func NewPortalLightApi() *PortalLightApi {
return &PortalLightApi{}
}
// ChainID implements ConsensusAPI.
func (p *PortalLightApi) ChainID() uint64 {
return 1
}
// GetCheckpointData implements ConsensusAPI.
func (p *PortalLightApi) GetBootstrap(blockRoot tree.Root) (common.SpecObj, error) {
bootstrapKey := &LightClientBootstrapKey{
BlockHash: blockRoot[:],
}
contentKeyBytes, err := bootstrapKey.MarshalSSZ()
if err != nil {
return nil, err
}
contentKey := storage.NewContentKey(LightClientBootstrap, contentKeyBytes).Encode()
// Get from local
contentId := p.portalProtocol.ToContentId(contentKey)
res, err := p.getContent(contentKey, contentId)
if err != nil {
return nil, err
}
forkedLightClientBootstrap := &ForkedLightClientBootstrap{}
err = forkedLightClientBootstrap.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
if err != nil {
return nil, err
}
return forkedLightClientBootstrap.Bootstrap, nil
}
// GetFinalityData implements ConsensusAPI.
func (p *PortalLightApi) GetFinalityUpdate() (common.SpecObj, error) {
// Get the finality update for the most recent finalized epoch. We use 0 as the finalized
// slot because the finalized slot is not known at this point and the protocol is
// designed to return the most recent which is > 0
finUpdateKey := &LightClientFinalityUpdateKey{
FinalizedSlot: 0,
}
contentKeyBytes, err := finUpdateKey.MarshalSSZ()
if err != nil {
return nil, err
}
contentKey := storage.NewContentKey(LightClientFinalityUpdate, contentKeyBytes).Encode()
// Get from local
contentId := p.portalProtocol.ToContentId(contentKey)
res, err := p.getContent(contentKey, contentId)
if err != nil {
return nil, err
}
finalityUpdate := &ForkedLightClientFinalityUpdate{}
err = finalityUpdate.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
if err != nil {
return nil, err
}
return finalityUpdate.LightClientFinalityUpdate, nil
}
// GetOptimisticData implements ConsensusAPI.
func (p *PortalLightApi) GetOptimisticUpdate() (common.SpecObj, error) {
currentSlot := p.spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(BeaconGenesisTime))
optimisticUpdateKey := &LightClientOptimisticUpdateKey{
OptimisticSlot: uint64(currentSlot),
}
contentKeyBytes, err := optimisticUpdateKey.MarshalSSZ()
if err != nil {
return nil, err
}
contentKey := storage.NewContentKey(LightClientOptimisticUpdate, contentKeyBytes).Encode()
// Get from local
contentId := p.portalProtocol.ToContentId(contentKey)
res, err := p.getContent(contentKey, contentId)
if err != nil {
return nil, err
}
optimisticUpdate := &ForkedLightClientOptimisticUpdate{}
err = optimisticUpdate.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
if err != nil {
return nil, err
}
return optimisticUpdate.LightClientOptimisticUpdate, nil
}
// GetUpdates implements ConsensusAPI.
func (p *PortalLightApi) GetUpdates(firstPeriod uint64, count uint64) ([]common.SpecObj, error) {
lightClientUpdateKey := &LightClientUpdateKey{
StartPeriod: firstPeriod,
Count: count,
}
contentKeyBytes, err := lightClientUpdateKey.MarshalSSZ()
if err != nil {
return nil, err
}
contentKey := storage.NewContentKey(LightClientUpdate, contentKeyBytes).Encode()
// Get from local
contentId := p.portalProtocol.ToContentId(contentKey)
data, err := p.getContent(contentKey, contentId)
if err != nil {
return nil, err
}
var lightClientUpdateRange LightClientUpdateRange = make([]ForkedLightClientUpdate, 0)
err = lightClientUpdateRange.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return nil, err
}
res := make([]common.SpecObj, len(lightClientUpdateRange))
for i, item := range lightClientUpdateRange {
res[i] = item.LightClientUpdate
}
return res, nil
}
// Name implements ConsensusAPI.
func (p *PortalLightApi) Name() string {
return "portal"
}
func (p *PortalLightApi) getContent(contentKey, contentId []byte) ([]byte, error) {
res, err := p.portalProtocol.Get(contentKey, contentId)
// other error
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
return nil, err
}
if res == nil {
// Get from remote
res, _, err = p.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
return nil, err
}
}
return res, nil
}

View file

@ -1,45 +0,0 @@
package beacon
const CreateQueryDBBeacon = `CREATE TABLE IF NOT EXISTS beacon (
content_id blob PRIMARY KEY,
content_key blob NOT NULL,
content_value blob NOT NULL,
content_size INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS beacon_content_size_idx ON beacon(content_size);`
const InsertQueryBeacon = `INSERT OR IGNORE INTO beacon (content_id, content_key, content_value, content_size)
VALUES (?1, ?2, ?3, ?4)`
const DeleteQueryBeacon = `DELETE FROM beacon
WHERE content_id = (?1)`
const ContentKeyLookupQueryBeacon = `SELECT content_key FROM beacon WHERE content_id = (?1) LIMIT 1`
const ContentValueLookupQueryBeacon = `SELECT content_value FROM beacon WHERE content_id = (?1) LIMIT 1`
const TotalDataSizeQueryBeacon = "SELECT TOTAL(content_size) FROM beacon"
const TotalEntryCountQueryBeacon = "SELECT COUNT(*) FROM beacon"
const ContentSizeLookupQueryBeacon = "SELECT content_size FROM beacon WHERE content_id = (?1)"
const LCUpdateCreateTable = `CREATE TABLE IF NOT EXISTS lc_update (
period INTEGER PRIMARY KEY,
value BLOB NOT NULL,
score INTEGER NOT NULL,
update_size INTEGER
);
CREATE INDEX IF NOT EXISTS update_size_idx ON lc_update(update_size);
DROP INDEX IF EXISTS period_idx;`
const InsertLCUpdateQuery = `INSERT OR IGNORE INTO lc_update (period, value, score, update_size)
VALUES (?1, ?2, ?3, ?4)`
const LCUpdateLookupQuery = `SELECT value FROM lc_update WHERE period = (?1) LIMIT 1`
const LCUpdateLookupQueryByRange = `SELECT value FROM lc_update WHERE period >= (?1) AND period < (?2)`
const LCUpdatePeriodLookupQuery = `SELECT period FROM lc_update WHERE period = (?1) LIMIT 1`
const LCUpdateTotalSizeQuery = `SELECT TOTAL(update_size) FROM lc_update`

View file

@ -1,204 +0,0 @@
package beacon
import (
"bytes"
"context"
"database/sql"
"errors"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/holiman/uint256"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/ztyp/codec"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
)
const BytesInMB uint64 = 1000 * 1000
type Storage struct {
storageCapacityInBytes uint64
db *sql.DB
log log.Logger
spec *common.Spec
cache *beaconStorageCache
}
var portalStorageMetrics *portalwire.PortalStorageMetrics
type beaconStorageCache struct {
OptimisticUpdate []byte
FinalityUpdate []byte
}
var _ storage.ContentStorage = &Storage{}
func NewBeaconStorage(config storage.PortalStorageConfig) (storage.ContentStorage, error) {
bs := &Storage{
storageCapacityInBytes: config.StorageCapacityMB * BytesInMB,
db: config.DB,
log: log.New("beacon_storage"),
spec: config.Spec,
cache: &beaconStorageCache{},
}
if err := bs.setup(); err != nil {
return nil, err
}
var err error
portalStorageMetrics, err = portalwire.NewPortalStorageMetrics(config.NetworkName, config.DB)
if err != nil {
return nil, err
}
return bs, nil
}
func (bs *Storage) setup() error {
if _, err := bs.db.Exec(CreateQueryDBBeacon); err != nil {
return err
}
if _, err := bs.db.Exec(LCUpdateCreateTable); err != nil {
return err
}
return nil
}
func (bs *Storage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
switch storage.ContentType(contentKey[0]) {
case LightClientBootstrap:
return bs.getContentValue(contentId)
case LightClientUpdate:
lightClientUpdateKey := new(LightClientUpdateKey)
err := lightClientUpdateKey.UnmarshalSSZ(contentKey[1:])
if err != nil {
return nil, err
}
return bs.getLcUpdateValueByRange(lightClientUpdateKey.StartPeriod, lightClientUpdateKey.StartPeriod+lightClientUpdateKey.Count)
case LightClientFinalityUpdate:
if bs.cache.FinalityUpdate == nil {
return nil, storage.ErrContentNotFound
}
return bs.cache.FinalityUpdate, nil
case LightClientOptimisticUpdate:
if bs.cache.OptimisticUpdate == nil {
return nil, storage.ErrContentNotFound
}
return bs.cache.OptimisticUpdate, nil
}
return nil, nil
}
func (bs *Storage) Put(contentKey []byte, contentId []byte, content []byte) error {
switch storage.ContentType(contentKey[0]) {
case LightClientBootstrap:
return bs.putContentValue(contentId, contentKey, content)
case LightClientUpdate:
lightClientUpdateKey := new(LightClientUpdateKey)
err := lightClientUpdateKey.UnmarshalSSZ(contentKey[1:])
if err != nil {
return err
}
lightClientUpdateRange := new(LightClientUpdateRange)
reader := codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content)))
err = lightClientUpdateRange.Deserialize(bs.spec, reader)
if err != nil {
return err
}
for index, update := range *lightClientUpdateRange {
var buf bytes.Buffer
writer := codec.NewEncodingWriter(&buf)
err := update.Serialize(bs.spec, writer)
if err != nil {
return err
}
period := lightClientUpdateKey.StartPeriod + uint64(index)
err = bs.putLcUpdate(period, buf.Bytes())
if err != nil {
return err
}
}
return nil
case LightClientFinalityUpdate:
bs.cache.FinalityUpdate = content
return nil
case LightClientOptimisticUpdate:
bs.cache.OptimisticUpdate = content
return nil
}
return nil
}
func (bs *Storage) Radius() *uint256.Int {
return storage.MaxDistance
}
func (bs *Storage) getContentValue(contentId []byte) ([]byte, error) {
res := make([]byte, 0)
err := bs.db.QueryRowContext(context.Background(), ContentValueLookupQueryBeacon, contentId).Scan(&res)
if errors.Is(err, sql.ErrNoRows) {
return nil, storage.ErrContentNotFound
}
return res, err
}
func (bs *Storage) getLcUpdateValueByRange(start, end uint64) ([]byte, error) {
// LightClientUpdateRange := make([]ForkedLightClientUpdate, 0)
var lightClientUpdateRange LightClientUpdateRange
rows, err := bs.db.QueryContext(context.Background(), LCUpdateLookupQueryByRange, start, end)
if err != nil {
return nil, err
}
hasData := false
defer func(rows *sql.Rows) {
err = rows.Close()
if err != nil {
bs.log.Error("failed to close rows", "err", err)
}
}(rows)
for rows.Next() {
hasData = true
var val []byte
err = rows.Scan(&val)
if err != nil {
return nil, err
}
update := new(ForkedLightClientUpdate)
dec := codec.NewDecodingReader(bytes.NewReader(val), uint64(len(val)))
err = update.Deserialize(bs.spec, dec)
if err != nil {
return nil, err
}
lightClientUpdateRange = append(lightClientUpdateRange, *update)
}
if !hasData {
return nil, storage.ErrContentNotFound
}
var buf bytes.Buffer
err = lightClientUpdateRange.Serialize(bs.spec, codec.NewEncodingWriter(&buf))
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (bs *Storage) putContentValue(contentId, contentKey, value []byte) error {
length := 32 + len(contentKey) + len(value)
_, err := bs.db.ExecContext(context.Background(), InsertQueryBeacon, contentId, contentKey, value, length)
if metrics.Enabled && err == nil {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(value)))
}
return err
}
func (bs *Storage) putLcUpdate(period uint64, value []byte) error {
_, err := bs.db.ExecContext(context.Background(), InsertLCUpdateQuery, period, value, 0, len(value))
if metrics.Enabled && err == nil {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(value)))
}
return err
}

View file

@ -1,115 +0,0 @@
package beacon
import (
"crypto/sha256"
"database/sql"
"encoding/json"
"fmt"
"os"
"path"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
_ "github.com/mattn/go-sqlite3"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/stretchr/testify/require"
)
var zeroNodeId = uint256.NewInt(0).Bytes32()
const dbName = "beacon.sqlite"
func defaultContentIdFunc(contentKey []byte) []byte {
digest := sha256.Sum256(contentKey)
return digest[:]
}
func TestGetAndPut(t *testing.T) {
testDir := "./"
beaconStorage, err := genStorage(testDir)
require.NoError(t, err)
defer clearNodeData(testDir)
testData, err := getTestData()
require.NoError(t, err)
for _, entry := range testData {
key := entry.key
value := entry.value
contentId := defaultContentIdFunc(key)
_, err = beaconStorage.Get(key, contentId)
require.Equal(t, storage.ErrContentNotFound, err)
err = beaconStorage.Put(key, contentId, value)
require.NoError(t, err)
res, err := beaconStorage.Get(key, contentId)
require.NoError(t, err)
require.Equal(t, value, res)
}
}
func genStorage(testDir string) (storage.ContentStorage, error) {
err := os.MkdirAll(testDir, 0755)
if err != nil {
return nil, err
}
db, err := sql.Open("sqlite3", path.Join(testDir, dbName))
if err != nil {
return nil, err
}
config := &storage.PortalStorageConfig{
StorageCapacityMB: 1000,
DB: db,
NodeId: enode.ID(zeroNodeId),
Spec: configs.Mainnet,
}
return NewBeaconStorage(*config)
}
type entry struct {
key []byte
value []byte
}
func getTestData() ([]entry, error) {
baseDir := "./testdata"
items, err := os.ReadDir(baseDir)
if err != nil {
return nil, err
}
entries := make([]entry, 0)
for _, item := range items {
if !item.IsDir() {
f, err := os.ReadFile(fmt.Sprintf("%s/%s", baseDir, item.Name()))
if err != nil {
return nil, err
}
var result map[string]map[string]string
err = json.Unmarshal(f, &result)
if err != nil {
return nil, err
}
for _, v := range result {
entries = append(entries, entry{
key: hexutil.MustDecode(v["content_key"]),
value: hexutil.MustDecode(v["content_value"]),
})
}
}
}
return entries, nil
}
func clearNodeData(nodeDataDir string) {
err := os.Remove(fmt.Sprintf("%s%s", nodeDataDir, dbName))
if err != nil {
fmt.Println(err)
}
}

View file

@ -1,244 +0,0 @@
package beacon
import (
"bytes"
"context"
"fmt"
"net"
"os"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
ssz "github.com/ferranbt/fastssz"
"github.com/golang/snappy"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
func SetupBeaconNetwork(addr string, bootNodes []*enode.Node) (*BeaconNetwork, error) {
conf := portalwire.DefaultPortalProtocolConfig()
if addr != "" {
conf.ListenAddr = addr
}
if bootNodes != nil {
conf.BootstrapNodes = bootNodes
}
addr1, err := net.ResolveUDPAddr("udp", conf.ListenAddr)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr1)
if err != nil {
return nil, err
}
privKey, err := crypto.GenerateKey()
if err != nil {
panic("couldn't generate key: " + err.Error())
}
discCfg := discover.Config{
PrivateKey: privKey,
NetRestrict: conf.NetRestrict,
Bootnodes: conf.BootstrapNodes,
}
nodeDB, err := enode.OpenDB(conf.NodeDBPath)
if err != nil {
return nil, err
}
localNode := enode.NewLocalNode(nodeDB, privKey)
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(portalwire.Tag)
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err
}
contentQueue := make(chan *portalwire.ContentElement, 50)
utpSocket := portalwire.NewPortalUtp(context.Background(), conf, discV5, conn)
portalProtocol, err := portalwire.NewPortalProtocol(conf, portalwire.Beacon, privKey, conn, localNode, discV5, utpSocket, &storage.MockStorage{Db: make(map[string][]byte)}, contentQueue)
if err != nil {
return nil, err
}
return NewBeaconNetwork(portalProtocol), nil
}
func GetLightClientBootstrap(number uint8) (ForkedLightClientBootstrap, error) {
file, err := os.ReadFile(fmt.Sprintf("testdata/beacon/LightClientBootstrap/ssz_random/case_%d/serialized.ssz_snappy", number))
if err != nil {
return ForkedLightClientBootstrap{}, err
}
data, err := snappy.Decode(nil, file)
if err != nil {
return ForkedLightClientBootstrap{}, err
}
bootstrap := &ForkedLightClientBootstrap{}
forkData := make([]byte, 0)
forkData = append(forkData, Capella[:]...)
forkData = append(forkData, data...)
err = bootstrap.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(forkData), uint64(len(forkData))))
if err != nil {
return ForkedLightClientBootstrap{}, err
}
return *bootstrap, nil
}
func GetClientUpdate(number uint8) (ForkedLightClientUpdate, error) {
file, err := os.ReadFile(fmt.Sprintf("testdata/beacon/LightClientUpdate/ssz_random/case_%d/serialized.ssz_snappy", number))
if err != nil {
return ForkedLightClientUpdate{}, err
}
data, err := snappy.Decode(nil, file)
if err != nil {
return ForkedLightClientUpdate{}, err
}
update := &ForkedLightClientUpdate{}
forkData := make([]byte, 0)
forkData = append(forkData, Capella[:]...)
forkData = append(forkData, data...)
err = update.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(forkData), uint64(len(forkData))))
if err != nil {
return ForkedLightClientUpdate{}, err
}
return *update, nil
}
func GetLightClientFinalityUpdate(number uint8) (ForkedLightClientFinalityUpdate, error) {
file, err := os.ReadFile(fmt.Sprintf("testdata/beacon/deneb/LightClientFinalityUpdate/ssz_random/case_%d/serialized.ssz_snappy", number))
if err != nil {
return ForkedLightClientFinalityUpdate{}, err
}
data, err := snappy.Decode(nil, file)
if err != nil {
return ForkedLightClientFinalityUpdate{}, err
}
update := &deneb.LightClientFinalityUpdate{}
err = update.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return ForkedLightClientFinalityUpdate{}, err
}
bootstrap := &ForkedLightClientFinalityUpdate{
ForkDigest: Deneb,
LightClientFinalityUpdate: update,
}
return *bootstrap, nil
}
func GetLightClientOptimisticUpdate(number uint8) (ForkedLightClientOptimisticUpdate, error) {
file, err := os.ReadFile(fmt.Sprintf("testdata/beacon/deneb/LightClientOptimisticUpdate/ssz_random/case_%d/serialized.ssz_snappy", number))
if err != nil {
return ForkedLightClientOptimisticUpdate{}, err
}
data, err := snappy.Decode(nil, file)
if err != nil {
return ForkedLightClientOptimisticUpdate{}, err
}
bootstrap := &ForkedLightClientOptimisticUpdate{}
forkData := make([]byte, 0)
forkData = append(forkData, Deneb[:]...)
forkData = append(forkData, data...)
err = bootstrap.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(forkData), uint64(len(forkData))))
if err != nil {
return ForkedLightClientOptimisticUpdate{}, err
}
return *bootstrap, nil
}
func GetHistorySummariesWithProof() (HistoricalSummariesWithProof, common.Root, error) {
file, err := os.ReadFile("testdata/beacon/BeaconState/ssz_random/case_0/serialized.ssz_snappy")
if err != nil {
return HistoricalSummariesWithProof{}, common.Root{}, err
}
data, err := snappy.Decode(nil, file)
if err != nil {
return HistoricalSummariesWithProof{}, common.Root{}, err
}
beaconState := &deneb.BeaconState{}
err = beaconState.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
if err != nil {
return HistoricalSummariesWithProof{}, common.Root{}, err
}
root := beaconState.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
proof, err := BuildHistoricalSummariesProof(*beaconState)
if err != nil {
return HistoricalSummariesWithProof{}, common.Root{}, err
}
summariesProof := [5]common.Bytes32{tree.Root(proof[0]), tree.Root(proof[1]), tree.Root(proof[2]), tree.Root(proof[3]), tree.Root(proof[4])}
return HistoricalSummariesWithProof{
EPOCH: common.Epoch(uint64(beaconState.Slot) / 32),
HistoricalSummaries: beaconState.HistoricalSummaries,
Proof: HistoricalSummariesProof{
Proof: summariesProof,
},
}, root, nil
}
func BuildHistoricalSummariesProof(beaconState deneb.BeaconState) ([][]byte, error) {
leaves := make([][32]byte, 32)
leaves[0] = beaconState.GenesisTime.HashTreeRoot(tree.GetHashFn())
leaves[1] = beaconState.GenesisValidatorsRoot.HashTreeRoot(tree.GetHashFn())
leaves[2] = beaconState.Slot.HashTreeRoot(tree.GetHashFn())
leaves[3] = beaconState.Fork.HashTreeRoot(tree.GetHashFn())
leaves[4] = beaconState.LatestBlockHeader.HashTreeRoot(tree.GetHashFn())
leaves[5] = beaconState.BlockRoots.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[6] = beaconState.StateRoots.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[7] = beaconState.HistoricalRoots.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[8] = beaconState.Eth1Data.HashTreeRoot(tree.GetHashFn())
leaves[9] = beaconState.Eth1DataVotes.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[10] = beaconState.Eth1DepositIndex.HashTreeRoot(tree.GetHashFn())
leaves[11] = beaconState.Validators.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[12] = beaconState.Balances.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[13] = beaconState.RandaoMixes.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[14] = beaconState.Slashings.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[15] = beaconState.PreviousEpochParticipation.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[16] = beaconState.CurrentEpochParticipation.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[17] = beaconState.JustificationBits.HashTreeRoot(tree.GetHashFn())
leaves[18] = beaconState.PreviousJustifiedCheckpoint.HashTreeRoot(tree.GetHashFn())
leaves[19] = beaconState.CurrentJustifiedCheckpoint.HashTreeRoot(tree.GetHashFn())
leaves[20] = beaconState.FinalizedCheckpoint.HashTreeRoot(tree.GetHashFn())
leaves[21] = beaconState.InactivityScores.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[22] = beaconState.CurrentSyncCommittee.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[23] = beaconState.NextSyncCommittee.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[24] = beaconState.LatestExecutionPayloadHeader.HashTreeRoot(tree.GetHashFn())
leaves[25] = beaconState.NextWithdrawalIndex.HashTreeRoot(tree.GetHashFn())
leaves[26] = beaconState.NextWithdrawalValidatorIndex.HashTreeRoot(tree.GetHashFn())
leaves[27] = beaconState.HistoricalSummaries.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
leaves[28] = tree.Root{}
leaves[29] = tree.Root{}
leaves[30] = tree.Root{}
leaves[31] = tree.Root{}
leavesBytes := make([][]byte, 0)
for _, item := range leaves {
dest := make([]byte, len(item))
copy(dest, item[:])
leavesBytes = append(leavesBytes, dest)
}
chunks, err := ssz.TreeFromChunks(leavesBytes)
if err != nil {
return nil, err
}
proof, err := chunks.Prove(59)
if err != nil {
return nil, err
}
return proof.Hashes, nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,535 +0,0 @@
header:
beacon: {slot: 1749391663927035395, proposer_index: 302062673341048006, parent_root: '0xdc3f8320f7aed2168af36c5380e0be2b9a3c02991c9d1cb9f221357928946d7b',
state_root: '0x4e81aa953c4c83d850b3a8d89374fc3fb339d61e2073edb9a39e4eb37b455497',
body_root: '0x9eb9f39c2e88739dc4274483d1e9072c3685864d5bcbf2f3c072b97f54536a4e'}
execution: {parent_hash: '0x50ab9af1b41b6d53aa201417d204944112fd64753ad0fc490e629ad2e42c1aa3',
fee_recipient: '0x7c39bd000d85e113cb5aa85615e26f8461642566', state_root: '0x1a02d91b9eabbd73997bc352a779e540ed17235039f48fa46866c1ed65dae5e9',
receipts_root: '0x5f449ea641e593e848f634e876dbd9f4d8b2ac7064124fcfde79b27e86f1e2e4',
logs_bloom: '0xb52a7317363e97bb424fc651a3e4351c570fda3fcdd5c1890a42bd09fabb92a0555c905e678072545b25db70b1b73d23306da03b6660283a9f5e0d2daeee6d2a1cb6a095164f698ed7090cc753dbb552c3ee6d570d8c5846c92459e4f6343ad4286b6acf99861179d44c2d4a5092a220287b027034962a451c3a0d50f8066b0c66ca1b057843745b65033186558c4392bd67416f8adf6551e71549d2d57492386c7555b9d408e1242b5650679a905cd8eb0233c4875389949340269be09d619d61a965d28e28d288bb7a52e0dfcb9e90b943a36f5506b43ecbdd145884ec1d4c768348ee1a26ad9bc8b3ad963c7e9ebf78bbe5068fa49a1902c49779a75955e5',
prev_randao: '0x793e7b97a75d62ed6ac02e987b40febf92d996ae436e663a1aed3b614625137c',
block_number: 1266720819305267017, gas_limit: 12961647605337525919, gas_used: 4427170423445829134,
timestamp: 10921268395636722524, extra_data: '0xb59679f80d4098d46db204981af4ea246c7843a5e485',
base_fee_per_gas: '91716394047965960266411282978615492013838857639141879667104906043877891453430',
block_hash: '0xa0729484b7eb526f2edbeda828730b21907465e289aea0b64166e63cb7c51df9',
transactions_root: '0x09c33503d357154c5e39213deb6e1c686b50fd97e17cf4c24131f4016f4d78d6',
withdrawals_root: '0xd327e2d43fedf327de8f01ab8bbf4fbdbfececc890c5059ad53cfb37264cc03c'}
execution_branch: ['0x4138a3e27e9aea5c1c9eb311a7889bfddeb3d2ad3987943d68ee7cc659b6d947',
'0xd65d8e1789191903200b38445856f86965b4f90f4a3c92e5ef5a6674026681ed', '0x5d84200d5b2609953da8591c1ddfa7e93f5dd26e45721dd777dd91b8795c65cc',
'0xddb0c647c88317b392e78dfabd186cd237b688413e01d2d43fa1310acc54c709']
current_sync_committee:
pubkeys: ['0x009c6eacc6e3f29b10763a446fc6a6dfff95b408fb44c92b453e1349572d2b45b77305668349d39360e775752a4faf9a',
'0xf90f201041f9f9465baac865fde4f6954bb1de26ffec13b77606a05b138e96303614f6c1b67c0f67f7e63e2d29cba2a9',
'0xbc3cfead49967fdda9533055028c3fd8080bd4c8cdd9ef1a1275f22408a7f4bdab267c36384123966545328f17216575',
'0xeea62e628ca79472869a4465f78d76e308ed8786109da7026cbf70f741ea80a5b806d4f3336d49eb92503e8f6328f683',
'0x3ab208dc5c203dae7a2da0aebde7ca1149c2018fb27b09711325007671c80ef376d7449b70a970239ee320f85dfc0740',
'0xab934389bb73f1e59a1ad8e6b540999683714526b56b1114e11cd46c68de814fa43fd8132c30208f9c27cfaf3cf757db',
'0x3bb40a1eae6f171a34d01f7421fd953a0121ff1e68dc84522211a0ed8554b1eefbf95fb4ef708ea0364e90cefe794c92',
'0x83d462ec7c3754ab7ec175598d50f2311975c758732917462ce50cbaf1364897462b004b48daa7fdd5773a22c62abd6d',
'0xcfb327149d5bed1d979d41a27e1caffc7a111a9bc0bd5f9d961878cab8d106fb96e9d6985379dccf973238d2ef65e8a8',
'0x63e2b574960f9109116bef6a6e157684339f7212b81ec4ffccf7b6fa55035737a3d57dba5a6e1d82673dcb05be15b0dd',
'0x1da63510813fa14beb4d7807daf0c8eda40c0f4d795f9028bef89f09c846c5713dda562a88d26eb6efecbe4f4cf0fcfa',
'0x5c1c3076c9e932a699c15999e741b0fe407d6d3a780e58dfa10dd13838c874e581e441af413f2a20cc55d368bf9a6b07',
'0x01888403a938861c16e4fd0c4e2a8f2cee8ab8887f8f4a813f616f1f467e398d5fa8dff9484aa2c31e0c8d2fa09f23ec',
'0xa2fe1ccb9e4c6853ac7fa4c902585ceb5334e5610eeaeae77fd8e94e5f3b6087d311b2efbc56525ae64150d766e24567',
'0xf57945b695cbb0ae084fcf3c5e3927a2a290b7076d738e81b5a3039bba4bbd9f977ff4eccf0ff3895f947559a8dac208',
'0x35296b9272f7f75147692dbff84845a83826b45bcd64b9c4360aa0bd8f2435fe9b3262f428fd81a5aa10697d844f190d',
'0x184363abc094c029989e066afdc16c993fbe4cfcec413736ef31a2df903980d7611bc53d90784e0de9f15fb51043718b',
'0x17ce0c02b31a871d0aee8d5d355df1de200d2a4f5ed51ee54780143e910dde891c84893540e6023d97246228366dfe48',
'0xd9588190b902483fef7f048d833f040d5c2bfef8e20a6b6a9de1f2c86085bb5e17cf28da2a9fd6cb8097b72f59443b1c',
'0xa161df68a57bca18a39d0fb2618944d80b20ed612235fdec332beceaa633265fb7316beaed85e396254256f06ebe7f41',
'0x2d2379c0e55a67002761e3c37e53ecb9fca02e12310cd2c6d15be8ca04a1eb4cec363836284532fb9e06ca7780065203',
'0xf549bd81d448c370a31a1f58e1a289848908330442b6748869289f4324d257905bd51866f0140c873ca72c109e05b540',
'0xf42f4c259ade058bdb56625d748c18c405873a986078296ff31737dcbb558f10a1841747131e7086d6a8ff9f576e8081',
'0x02d4f24176123fb771336a80f6afb719ec6fb13dc41cd1f4564d28eb765d9f7d2bb942f104a1d1d3f38700f5d85f4a09',
'0xc6dd88534b1efad16e1bd2d0463f62a9ce47f39c9e02b734c130fad23188e978a9cd26b82e3434ac405c6cb3ee54f31e',
'0x141f75d363cbf72ff34d9bd77dff699f70cafebf90716944c128835d5fb69aa8b711ac118d9b45e4b68af8adefc1a771',
'0x1aa4266d1477196655f2cee140613d743823bc0b96acff9ee157242a55ec6c15da1f84988a57ff88811da418de3484da',
'0x86e7bf991e594b0960382bb59869032f00aab0b198dfb27eef4439eb804240fe0ed5b490b365f6e0d2a259b4cc6e4b3d',
'0x72736a4ebb6442745575e1be89174e9b767f5e5a5f3d0834c6fea68424852bcff87c0398f0b07f648608fbb77e9610c3',
'0xf094d230e0c35170887636e2467a869c6eaaab93f914b5147ba417f843b37f4b39c3a38f1ba732426e05a1e6a7c4e551',
'0xa1f1c587b6f81f532ccaecdd08eb46e67dd816c91de064068f48291bff171664a9da37672047af0932786a5a394fd1e3',
'0x64965aab6a06d956131d639447b35980f4e1a2e0fba9ef619c5792f0b5b5674db22fb639392eb6616647404ffc114b6c',
'0x1be43898c4cd30a2edb47d02ca6791bd840e05f0b87b7499125ce14db168cc5393e29607566b5a3cdc6e7136b63a91b3',
'0x9c0b0763c60f8b61d605f1ed312fbfd3292a63ed75af202017be06f8f651ef0b31cc582a12b9c9aac9e4cf0a24e281cd',
'0x717eeeb6a68bf419ecf4fe356ff221e62566afc992580d9fd72181106ca11d11e650d8d3c8fe50ca5c02a4d4b5184c0c',
'0xd16fa5b10cffe83fb69eab9a705adf70176528c17b2c05d4a3bc72771b9215cf0fb8acc5a60c723aed476271a2072197',
'0xbfb64d6435f8c7d45fce13b3161b2dd19f26f9464729061abd52d8481414860bac0032be684b75383639041edae3b2ea',
'0xde5338bfc6d2081c46acb791127811cba05bb3bee3a4435877c2316430a49c439f123e780f0286d5c4c7b759912c6237',
'0xea1095e26b112a15dd8a7be52efd891e73016cd76ec5b2a5a824135c73d57564bf993e8800e33c698ed8cbf3e984ee9e',
'0x609b9b7cbd4650f71276edd242047ccbb51e21b217712d18bd366b767ec875330ed17c487a8f9bd5b6ca8a2124b79bac',
'0xaef9fedccca4230a3aaa057f22557fd931a650ec2bcced97e70222c1bad1f2b28b534b5050dad06b3bf6b0eb44559f30',
'0x6c599ef1533cfd024131d1e075932c1d8b2a0fd2acfd9acdcb7a80f9c3f828db2b58502a42c3d9b6f1f66ee809af130f',
'0x94e96ef6dbdbb6a27285d2c640d4f5fb649bcee594f68e5a172464923424dd351811f48f60545c3c2ba2ae1520213467',
'0x78a530817b53d833deca3a5af6cd020b56fe32e1c16d0a4e537969f16b2ed92307c89495c3e48d87fd341104538f15bb',
'0xf597c27105d2063bd85acb15f650354d49087f43127a6abfc220b2a6aabd1a277a1ba779d60d6c5268d54177048cbf56',
'0xda6ce84509bde2f09d1ea94f4d69c2c84a7ccc888cb8eca8702b4238500f6e255170f2889910d8fbb64d4fd55caa7972',
'0x7168dd40369fb03c907463f6c271c07fbd1bb24e55dc2e589eab3280a7429513fc9d0fa76871d0620863cae7883e27b0',
'0x9a692f26e70db16a71009506170a05774908499944c8ad5e771af152fb8c5b257964df275bf7a1243db36460dfa65e43',
'0x0b6fd15b7a3f1633c85ac29089e57ac8dfecb2f77acf2740fec22a36e0a98c90dbd8e4acd926b5b774a002170d7aca85',
'0x6c9bc06547dcf6c1b5ac33e3f34af467ec13c300eaf0926698140aaa43569b201e3f07c583bab5a1e0358f410fe9d69f',
'0xcefb01f6351b729c25c928a08595f65787e371088a0ee9f67bb840ec300c7616a59986b30de81bf22f23ce640fd69812',
'0x954c6811ac272d292237cc6515693776cca7e5ce6f7832b26e44e7279e8a0293b8bcae067b73bdd1a6b3f8949066b0de',
'0x00e34fd0e468a3953ba17c9edf53a87dac9c21379e453ce7d62fa2d0090d853f2f05ca4cd3e3d87b71cf8457c2c1a7ca',
'0x4a16ba9427bd5f1216c79cda2e7a0cb116fa6c2f594e5296962970262a77048734aa1d7cc0b92354a54790ff734ffefa',
'0x093ef66aacb22c45d0d79435387a198abe40890b444cfe3ee69c8d9142098895dfa210b6f79052dcd55606100572c530',
'0x84164b93ff296da8e5b696d5c335217098901ca9edbc7ba92e0d7a6e37d2f3e1d58d10fcb216acc03480b9fdf83039b3',
'0xa4b09366e9a655e4df798bb225f2497a8b78d64f420cbabc56d5f565086c87a59286ac371d15de303de5f88677f212cf',
'0x3add7fe160a5019012c1dc4752b886679110c70bd3d0eebecd9037b9676a283c0850d28ccd20f999cfd89c310e78f33c',
'0x42e93fc242751d3f017774f31de2cb333e7dcbddc0bc8404db8af67e4aaea13d0f457511a64648b268601bda30f6d020',
'0x22e6732eea87b4cb7ab26c9e68200ed4f4e12114f154063c80dfa265b4fb8a5ebc6e8043908a981f520a76cb151a544c',
'0x11c313bc6e39446980ecde347ba8d9c524450f6a5397260766dcff4939bfc0321a639d842512ad844ba23f69da702342',
'0x5d9acfe259dc3d177df5f17c58e2a7ab634e235765102b6bebaba71250231c580961d1b0c3a7892e4272d768645a602c',
'0x8e5ae9f608c37908af9fa61f2cb1d6b96cda08293c5e449f46d6f7d190326b532add094f5df4da6d8d4560aab6543bd2',
'0x18714da2bd983ac0203d288be0df270387131a16b5f2cde435252e92797bdbc1976c038d3e44e8b4a8d357226de86f1f',
'0x3db19a5c653aaa88209a0c862c891b60c7165cbdbc7aadfdef68621d13f79ed8a72baaf18a8be0c02a9bf8c645798702',
'0x6c317c1802986b867f0ab4df4d0234f1acebd9cacdcf98f9d15562a294cea174e3a754ab62384b268cdd7e619b06587e',
'0x5926f103d54ea2cd7faeb2ab258eea4c4638ebb16ccb17aec9c92c29dca34f2d4e665f5f394dbe8b5d1168062ce37dd6',
'0x5ea3ee667e41554357a1c7f9c4cb53763a5c72d12df7ed5beff62d707d86dfdd34de5b8c1cad7857be5f7d8fc309221c',
'0x7f8623f6144c8eb9dba5376f1fb518b003015846a8c67c4da49233c4bdbe57e5be67244477f55322c91d7f70084ce668',
'0xb72fd74c6f4c654654a5faeaedf5b6e722deb89dbb010434b6f1a3c95ddcfdfa29d39979113d9889bd8f843b3ad61dad',
'0xea4c2f64e021403fca931b0f6726eca344d7b62f299b9e75c0e1a01aee9fc0d7ef385e51f17fb2171d14bbd24bc66e08',
'0xe57e63e86588caa4466f9eb184a149d9db2c8c9c370ef92382523296a86213e8398010ac56d08a848b9c67b548239b5c',
'0x1bec1c2821f4714e0f1c10343b11c33d957a4123a2fedd2cecfee840c0d9d109810be22c9be66f5b44937eb73b7c78c1',
'0x5ca166313f8ded7954208699376c544612841be2ee3dfb03920ae29e4f751cfcecca7ec76becd9954ca58a750be9d273',
'0xc1822f7d8024878ecd82709927d2adc4ee32115f5e521e1f9f6bb7481cd4c99f77499c18fb76b1f8b9b3145c9ca838df',
'0xa9a9ed03bd1073f4ca374b04ee0e5e43ada0c89f7cea10d758c4ae65ae0970fd1b996454553efcfad6666d81ed5e163d',
'0x3093efa4fcc4f2e539c87e70731a66d67552e85d8877cf2195d7f11be3006e2883ae533572abc777e0c1bcd1ab6e574d',
'0x4b43634bfe0ee2eb8926f76e60714cd0b0ee8c24a55dff947588f165acea4d6289d923641288865b93c8b27db152fc8d',
'0xc3832f2c686465ac7f8054ddbdeb9ebe30971370484769bc25627f3096af0e5515b89b85ad0bf23a8998bea9c58998bb',
'0xc7ce344e48ac716c2a4c4b3f5f316a16d2dbfee439ac0934b7bb1e070484742d4d6fc11f99c0d2a112fdf0774d89e0bd',
'0xc74edcf758dffdf90c7a8f884b6aa54d293c63acaa4599a841e2c691e77eee59758f3a0871c13b38e75ca5040d0885d3',
'0xa185eb5c1029168cedf4ae8460763a42cd40a0d1cc214cc2fb9ae57e3b0ff6008f053f41f0f1df94085630109154c10b',
'0x526b7d6e286566ca05aca325c5e223bf8d68f0a6777f50b8dca8d25176adcf726d59c910f0184b31d654d01b5cc498d9',
'0xc34dadb1c83533f35957ca94c3ce302965bc834b23136b000fbc123f01eef46ccb33dbbce0e233e92d0c5156d6b5392c',
'0xf6a664b7bfda9024d39a092deec3ecebc4ea86411f4f7272471f55fb4dbab9f2a1c0b6fd07aa285d8a4a7ca16c535a47',
'0x91819b32225daf51cf86ca2fea56aa3e0dd86464496b5a9f8839bc3999177ea954667f94e7cd4dd9e1a7f598edeccfe1',
'0x67f090f9764837703a93df0b1516b182778f1e789fe8e83e49c0958a5d3d956829f94c8ecb8bf72cfbcef742d4ed1b86',
'0xb46bcf5d22e8a666d11d93f765cd4250e5616c47719c1380106edcea58f71f27c1ddcba427a2dda1381a4adaa1c1ad28',
'0x8aff57a08f5930e6cb7238b1c9bacdc04c0cd91ddff47d9e96ad6e0efb3683a9adeb332b12d488d9986b2298bf27bdc3',
'0xd6348355df51465cd740ab5775ccb9ac48cac4cb2f8d036724d498ad06be5ea689c9b3a364e3c4de7600c1ed25958b2b',
'0x06d82ff75365bd02d2bf769b139c27f59fa3333328fa53966fd69ea9bf26c6550054cbf9aab41e8f90e2307f4e4fcbfe',
'0x7d8056842f2479849ad1f31dbefb04e4b7dd4f66a8eef287e4aa514e5c6587918214d92e448c690b0c84035087726223',
'0xee95a66358abfbab73c5d7249002ef52d33cb3301d3ec97c499cd426b08953747ec6d4e70dc0933596b85c86d7daaaa9',
'0x3e9923cf592fbf3b2d454f61b304079ea0df459f9f38f31f8174c6d5284700699d65f7800eeff465d5e03f6cbf003842',
'0x9abef744c8c12bee1d546d9106ebb5157137fe0795b658ad79288b618dc0aa9c52c0939cf677de6ee32fa42236aa7248',
'0x718da6028f711e2db9f3a720ac185fec1414d8bb77efa159e8d7114553f036a3af8176148d473d201150e69c04059fc9',
'0x820a11890b15d38a877688f29d7bf727fe1ae25f4a678ec59bdd38843a620ed33b93de2934c7be1368522fea5e7b16f6',
'0xaecc23df877c4a36201b6c2967fae2890ef5c2aa495783c0f7ebec0ad22599a6d16bb50d880c7af7fb0da31960613f62',
'0xe700856dd260fad5fc9cef21370ac786616abf049b8d39925c2279d727744fa0def18778ecb6bf800ffa3718b82e2e25',
'0xf9e1d95df9f07717a2c504144b60018805073e72cadee8732f734d325eeecea0ec84809b43b3db30bda61c61c3a93038',
'0x593300c19fbd8bfa7365e1a9e86123173e7e521d9cdfb866d96ecea039c8f130cb2380309bceb8f6bce5194f5f07f72a',
'0x384b4ada1beec429fe1e8395e0412216899d081154a649fd283b95cb6054ade06fb82b6c55ed118ddd158cb581eb3cd8',
'0x831c3e352d7d98163bb8f751d10959590e100c0e8c1d0640f0e1bb2abdd82af533bdc9de7c190b41a8ca38d7825403b9',
'0xf7812cc46d4627dc27554a3bfb44c0af4213bb78448381b49141a5ff4fac3e204cd2ac55b4d0d3913c99ec22bef45c5e',
'0x59735090af6b3f04223234e03276c7f1cd1150eea93c2d4a1c66fab696c732b3c731e822efb285349d93ba9eb3c9acfc',
'0x1bd30c9ef9ef7c1e1ebd6618f3203acd9f8c512b78bead70cd7a623cd37b111e3c2a2624711ecfccd1966e79eedf09f0',
'0xbcf30c716834086e18cfe2cb434ff954b88cef245b45a604163f40f5bc9452e833a8059396532c78043a044fabac23c7',
'0xe33d4ab3c6af4725b12a79b32e407dc1b5bd6529b582d3464e96b4998285276eac5866431e760294e44d1561517f1a73',
'0x8735e395ffdaa446f683730846b4c6634248c721e5386f470c7fe89f8a438d8b72762a5cf259aa5ce29b0f318fb5ca80',
'0xc3d4e78028dfb09102ccf2a055eafeef46673a5462d210813cd6eb664bc44ba01176be992f13650ad463395b1dd09d30',
'0xeb99b3f3416db513192982d6a3f8a571ddca4226a2ff8c37be40307f9066d45d728c98a289b93301f21bffa3b8a2820e',
'0x991c71699ea18bbdacf0fb2d5ac0048e32eac249f59e5f917c5fc8772fae3151ea5e3b6fc28b9db01c9e502ca28b08b0',
'0xa6184b3d258c20fe3af1f09df1a3e2019255ee739147f384e41abe71b9e4628e56b5881fdab71b8fc6960f5cf998c70c',
'0xbab2e22c96beaa650876f2e203057d8eecdb5a2bbcc2ed6db770130a4dd11cdc6fc83aeaa66ac24e6562ac7db956545c',
'0x5630f263fe0d24514a5eb4ea518b17f19ffaa32bc04a9a01eefd0a9182c1a64ac543bf822d4787228acf959ded824889',
'0x43caea8e33324231eeee6c4d743dd86abc56ba8b871186eca00c171b325110e468bf73a3f632fa85627a882448bb6fe9',
'0x11cd6ba8f3dd0f3f407f4d03475771345f2a6e48cc0ca22d9947bd01e143d8962cf92630f5497f24ad2b36c11b30bbcc',
'0x18863d2b05aae3cd02602e0bc6a8bffa304e1e4666c42de35221d634b7628fb333c7c28dad8a46a5d3fba4967197249f',
'0xf03cfedd7a9d6b2c3b36d3cf553469bcdabbc8508bf0a573a89c8686cef4e41341fe0c411529f973dc3c1e4daf12374e',
'0x7fc77db91c8972de5b5b0f97d17cb93c6a53dd9fb488a8e6ad80fbc48de17f7d543fe7e02fcb921302b54a5bae66fb6f',
'0xff518436b5cd5d57999ce52e024fe094e3c44b6b61f0315ef83e3a88406ec3ad8983b58ca092920a51e34acf7a190302',
'0x0b54674fb89dd03c2830cfe9d2cf4729928fb9c8e93bee8d7f0e0727a04a58c56d2bbcb1b46bc3f5df37d1c75d6a096f',
'0x8fb5204ccba5c1ef0d00545bdb1f7b577508ab792c7c4cf02d2f9fa2e168dc5eb01e8a2ea26c4483468ef36826765e70',
'0x5f1ae2af94189e5c296a8e29402d2b3426f31c2b5898f7dcd53362002df059ff8a4cdd1f805f02880563cca500619808',
'0xfcf2b608f569e34b2a3f131a25634530ba330d9ff708d2dd4d2ea4a6a032f9cfa7c5341208bfab3973838f3840f5a1c0',
'0xa5a68574c913e7722e475e6754e7a7a2931e1823bf7c4cf2981d797eba75148656df002b350534db8714d35ca1b521d8',
'0xbc856b44b8b7e8e91359d86b1cd041af6d718ca9276b0274fa99af9e959d36da1ef80185385b18c38db783a54999a2fd',
'0xa37a6fdaa4c70cf46fee258b2ad189b59b1db8fff85985b777c1de67dda12b2a029ab8c1ab5b94a81ebeb9b077432f20',
'0x4f27a18e4a74656092905ba32261b047016f7399cb23296e1341a7487c6d939e73be695170b47d550f04bba77d51af8f',
'0xceeaf13f6ac8630c77f55fd417037b510f79fb04113eb7e02f65a74508a907f3e0200c3d516e6dd2db25621a8c3e6d67',
'0xbf9329a82c3345d349d806587d94e8ee58b3c6379b2a73ecd4739ec1afa05fb7bcc01c3e9490328659bb0a8ed1fb4de9',
'0x2065212ecddb11a386119ec953f75e200aa6106cd8becad8df5a2bcd60f54cb5eb6be4eabde72317d3baab6cd101c0f5',
'0xffb6a167b6b8d71f79b437dc1232d818925606e3039201f94246bc44bfef17492a0524df636776607f8ac18297e831e9',
'0xb2708099cdfe123c2a7aa846fa6ca7ad05a9d9f1c7ea88c29ed9f7741b2e3c750b62a988e341c1670d93b8f086d24764',
'0xe7f2b9f2cba54196f4af6af4a5479cd66fec1239434f64fa5021f67a6652579c3f7ef66fc47365f70731c12042e8a93b',
'0xf5889f7e885d9f5634ae1a4fbc5455fbff6a8e3ad8b32eda6158cc8e70f09c1267116b35a450a2e2c9bf8d643ae01bb1',
'0x38a535871c39da3487aa665b31a2578dde3a2fa95a78b3919baf531e93f6d06ff751dab24cf62b77af3aa89fe19850e5',
'0xe2b919a509eb72ee85ea0bc2d0e5f636a610c498673c8e322e9272fca8daac7ad85bb62f5fefd342a095800df0b492b0',
'0x82e6653b68939958f06953505ec32dbc5fb11bf79e02ae145088a797ba5f7c1752ece5a1b4c8ebe051e792f0117cd525',
'0x2c58c3b847757605fa4ec3b3dbc3f563c44da62b555d52ef9c47eb69115f0a092631650218acd0b5a31a628e2b06c872',
'0xe07a630aded7d8c2cc7ca49c8cf026b3361b78125fd477b3096d1756b1afedd4c28ca8131162df898a8d0c1f82ad93b0',
'0x00c798d9251bb9ea68f23ba69a8e16f4325317d3f23b17610b8ba79f3e089833e37a4047fbb67e0dc9a0d260a515f6e5',
'0x38e525085515a432102cd2fe76906e99d65762468bf2c5d74bfb2ed5fa73232756a399105181a7135ca96e57f8df0bb7',
'0x4593a7638c760851f3cf0dd39eb78c7834e1fbd2e40473cb2646c31751a90b4f1c00bb41713129d86a92641635eba30f',
'0xe48fbb7c15271086abfa1cf5c65bab8a8d6e1910da5c06843ce504cc6c10abc022867c91bb0949df9e12110e266327e8',
'0x4a9af1569b7a683e5c4b0dcc8bcd00ccdacade6c1772c5e8de7832ca129f41ded3c4e2056653e8a96f43a631d2f32e78',
'0xcf7664e20f7a25280fa91547a6e3e4f69e1787d2a1be927b71f6db1b7e7caa584dbece59e6ea9df42450c090f8db954d',
'0xd3399a7180394423a64522cde0be5f7ed0e208612fcbad1418cf417ce43096f40d35d08df3df51a83315e2e06dfe2a4d',
'0x3d7a8660804244cdc8acdaaf5b9f6c01a2fac2cdbc5622559939973af2d88d78fe4bfc94ae49c68e396343a6cf7652bf',
'0x4e9474b5bd527cc98453ab2140aeebeb558831f245d124827460cb28a7f09803c6e1713d7713e8869bb48305c77448db',
'0x0d013652bb739fe18f641a955c693614a367de660a206a86bf23eef25bf548e87b3b0434286a8a15aee72156348d87b6',
'0x01badd9394d99fdad8b370e5ff02e6554464b705b78b2226344e7a88727a675ebf3507678a1d1535c9e9cd3c636340af',
'0x91c006b1b41a32bd3614d4ae5f412b73bb98cb891bcd9be9ba00e8708a2cd5bafc4fca51eb7cc3c8bedbdd47aabfa183',
'0x68fcdb22501f5dc9b6a92b04193f444f82cc331ac0fd86760164b3fee813d0ec840935a96c9b68b29d8e937fccc86ef8',
'0x47c5b8751d0ca0e6b4e7c7be9fd7d2c27c4e961c44a2ee97aa6bdbd282cc4069b076b5233b1be877a4a87604d55a9300',
'0xdc43cf55469e7a583529d060e56f3274b96a140e680f0994a7bc91d04e0cc86ce09a1ba9e39d54edb3e4acd7f90bdf97',
'0xb110e1b22c5afb7c26bdb512f9b3898ad0a46ef475ff0deca0c97e47d9e652389b615f014953c4066d652403a1c4570e',
'0x48f0e41e2edb8febac01dbdbfbf7bc9cdf0265fa1168f97b4a841d3bda798d3277e909f92dba04477c26393974113279',
'0x08e63d64eaf7f4628777ea3dfe20dc120b76df2feaab9ddfcf6aecc8300825774e4cd32bc92f1eabe38bf660c17df1b8',
'0x6554e2e1e474b504220ed37985a62c44af5211129f1735be0fe31c51f70c6d4f9749b3bf0c49d544d27ef077850299f5',
'0x484f431fb678f7718ec227efc8e9d9e7c0ba688e2c613b6db62e3f3383dd0569c2662707a8ee49284b1ff4b6c9b92acc',
'0x3dd3ae59cc87cbeeadf328a757914454caf87338ff31eb3050d72d882b863e41f5f2009a37458331f4528cb2be705369',
'0xfab19aa2cc5f3ce64a4af37a7fdcc5f31f54ddc60cbee79f5baa78633ab2cd519f8fd7ffe62f97648a98086598740675',
'0x9b96bacc93b1a9b0826cb5c75b7b885dd877595adbe5bbd64e0eb77b802721ba1f2e29ff00c483c570c626bdc1c4a00a',
'0x616605b6ec9716c6a6680fde0ac7cde508bca040d4750182c4e47f37523a99a9907255d014815d2fe4ed794ea9bf14c0',
'0xabdc6fca6a951b564e0412e7e4e0e1784255160909e3150de9ccb463ac9227ff949204772d4e1a3d7cf3e22a7122db90',
'0x37207999474cdc21f43def2da933bee04334f06a4e2b63526e52d7810d5d884f3da7786b4d4b517cf368c1b638af085f',
'0x362251064b5460f6d51062d574dad31a65b8c39846ec2c26dc1f823888cc25f26ccef02f3375d31259b3c81ee249fe43',
'0xa148aabe172c73980d8b7ce0453113ffe231692515b8091af89c418b9da285aea2940f6246d991e870400bf9ac1e9aa5',
'0x90262cd8b64ab9e883e7200eb3bc63369efaa0da2033856e73756be9dbb099ddb6f7a020b2606a981d9b77b4be0e8d93',
'0xc994cfb5a68570c4fa9768c0ada7ea5a5fa56a7e42134a3680891f049c8a9ef866bdc8e4005833aa4f0f7649252e4e1f',
'0x9e7c6ab310787bb84d2b2eed7badb70af5919927dd416019dc5f2c8b7ca59098afe23d41a6988cca9c7eea9ab914a171',
'0xbabb9d720d1b4bb9fa4e1edc18707d84569908bcbd67f32866cad05d620431e0c183b4808f0639e8069df4e962f32e3e',
'0x2997b3bfe550a0dfe7f6f729a36a33838a7ebe90061eb8470b73869f93872ae37a715ec7afa05a6cd873061564bf0f25',
'0x24d7155183bcd57ed8c6085a790233699612ee8f30b44e4ec549a9c615dd46e84c641c4e4acc1df345e27da41c982fde',
'0xe9f56bae2ec0eb143856a62df2173c0f66f7e9f3c085b0ae34a4f85086b42971ddab1e6fd874afa3270b3beb7aff99a9',
'0x02283ed111744d65a1ac36a8bc64e8130ad7d9fb537884410d374035b649626bf11624d7a8ea09ea255a4c78ca691360',
'0xfba611e4258a94b5d9109660c051318d2797c2b817a58f467d1072092e54c952f12fbe74f99025141387a55e84740a51',
'0x33981f3f84a01ff0df86a4b2d95c562a34909584e860992afeee9d252c066c865e68161bb3b0d7710503465b23d2f646',
'0xd11c5d2a9a181044803f51402ee77a58a92902d2b30d02db46463dbccd63d9ae42d9b82539e15bde078a4e15b4e545d3',
'0x645602a6649b25619fcc3dd5d9c74b690755a92dc2a4c821db984d945d6f596558d44462a9edf738d4a8ffc32cbab792',
'0x4c122245cf28c997a4b8d3db3744b1c8ecc4fdb56b8ba1897b9e1ab22184ae4d63432fb6ab2ce71c1f78ddd478176fea',
'0x9e41e29ff7390952802f55f8b1738186d9bd69792eacd48b30e4a11ad6cf4f9b9455ec53d9dd0707c9049b542b460683',
'0x81a1e996a8be66e5a00b10612082123bf8cf85addb01d910653cc8f822e33daf9a69eea5edbcb0b0bf4e36f4c4018014',
'0x1dacda0387fc2a6aac4fb65d4021eff289a8efbdbdf336a612ebc5a6dc4652f66f0a01a339121c24d0f5c0fef5f8c0cb',
'0xdc06132c6af041c85b8ece3eac36f174f8c5db369f1a116ea894a4485634ef3fa4bdd0133104dbc615556ec693eb6b47',
'0x1e0c7469037c19855fae711c3f64c3c8e08dddf84c9445d21a71882834df4c2cf46b03cc91d4ebce0d7a584ccd251c2c',
'0x8f1d5b8e3b099f6b97c7936ca75a89ef6b0e8b4f47b10c13dfffda3aaec10c9ac8c660bf89ac7b6f0df7469f2bae8580',
'0x4c2b2ec5ef82d71564f2c390f8fa3fe207431832807d6dd836fa4aff9ca3129ab990b026b06fbb6aa6127721a436a9fe',
'0xe41f80d840cfd40cb643fd71875d6c9e86425e3016c2f8dd4b2081e9af85a7b96dbb78e6f7a484a30b17a11895aca6da',
'0x1c3bba0900faa9aadf5d747a9101e9e2df6cb81a268980b78dc40a86dd907a1e1540a992ab7402df35c009dda69bee5b',
'0x5d291ebb7ce833a40ca10f8d077a910b8f528c3d4843d168df796ddc8b1a74e857d2015bd8af88ef9a1a9f3e887f8134',
'0xb50c8a2186a1fc30e93fd29d04c4fe12fc9f555134dcdaa82d1d3881a094785c97660352d8c96b1117b7946b8fd1758b',
'0x13311366d52c5199323e031f71a8b08c07c9856602c2fa40957e8069c383ef438e5ae909c12dcb3ec1b3093d9755509d',
'0x5d18301782a922e5e125740005287c6979321a0b3821bbf23fb2e78daa15eeabf295557a5ba5fe97a25b6fb34e5014eb',
'0x43b275ae699dea133fd04cf26a6a5a451316babec235ab8d15bff2b73d64fac0dfef6c5aea17c452938f5697fb094587',
'0xb590a7d14e10631569fddb532d60e54444a2017c251dff9594a769c49edada09cdd06663d64c25113ed1de1a50e4471c',
'0x370fe53d8b5ac1d3cda847ab6ddb88f1b7f66be3c7a1823871944a6e3b478553744138fcbeb7ef7471b6c97c7193d5bd',
'0xc0f00cb169a0e46e0142c2d89d345fd2e2b48976d48b644a3c192a70906c70b2bc12f923f483e999fb7a9835fcd719de',
'0x7bb825737e25c3ef9cdbf81bc0254ad8217e18940dfa5366d2e087faa986a6b835b83adf7a297bbf278542cfae93335c',
'0xda2a9c51f6c3e354778bd528335c49b9ea6f8d412b65a67e398bbbd9fc55ab5df6fed13d911d789756d62941e4291e4a',
'0xc637b684e172eaf634443d93378a58b82902a7c60e9d5afe39cbc88492edbc851fd60c4a1f11c5a46cb00c306deee9a6',
'0xc8d556040639b198db8e9f0ed71e2adf62b3edc11f6562e20c6562fe5c1b5ce5b3a153bdad23d33f148466793050a0ed',
'0x954f84de04aee46501f87063e80d2d564c57c1733e2c5c21949601335f4e61f6809635c8653418c232a1124cd2479343',
'0x35d0f160fdf1010e9f57700bfcd72741f3f470aae3558f820538d23548f879c130de3faea29552d16eed68309d5ca90a',
'0x63572beb34451f5258a007cb9b4e8c16aca5978c4ecd8eff09592ac3b6f7d9b7d26ead2780a4bee0925cb53a22ef78dd',
'0x09e02ef1afd6f6bc60fa3a941acf1c5b52142dd760d607d0860a8a84c5f61a0197aea5086bd8d7a2010d1a67b8e868c3',
'0x71b9c68e8f1095aaa28b64ef3f402b57f06d2b497498ebdb19f109720874a6085922556e767cf8d901537487580ddc32',
'0x72dedfe2a0501e009232fd134bf31129b6b0e179f82de466f3fb88ae5a4ccdeff179fef3e788c67df4c2e19f3806f02c',
'0x91374d5034c294356dca3772c386e9350d0ac97a2b94173301b692250e327f37d9757e60001bf4c0bcf369719bfe221d',
'0xe1566860c6f55763980ea948b15ec481f7fd3f81c6953aca46973e1a9cbf38eee0f96c4124aa4b529e6d0d0a125c2a0b',
'0xddfe4bcdddd5a05ca4d675e117bd15226d0ebdc682c6adfbfd22f279e61f0f93df40838e044d70cfb6116e331f4b78c2',
'0x491317f541c28be3b9551d812a2e5f3026858149cd1df215d1fc8e6426a6c42028a79f91820dd9ed30d040e5e8093d1c',
'0xbcb7cd119ad4eb4c7f87c06292d861397910901c621f6072c89fcdda5bf03dab5e1a87784c9ac8c5a8ae09ef75523b22',
'0xc9abc531823dcd8f5c6807ac2e3fb6b3eea2b5e44098043f4ee0250a9bf49a14be9a96d37b0522ba5702e69b2388f682',
'0x2a169f47676c7c3cca8b873eda2dddcea50a8c4dca1b1151e47200f7189b38d324f8089b0100d0abd1f6ed1082bd2af9',
'0x584d2126adc5f10cf5ba36398746c80404bcd37ac95ef79431eabf7af11eb70d076eb865ca302476fdc2357cf94b0cdd',
'0xb669f83cdac22a55cabcf734daa7f911e8985795add203baf0fabeebff915554f727af2b737a77bb34e5b6089e9fcd7c',
'0x353ca16ff62a348244e5b961af4ff5306adb0635bb2eff759a1dbdcca713d3c3dc85bbd6621f871b6618c06a1527f1e7',
'0x47a2605d6be1028b6f11458ee845d699913eaa53af0e193902dc8cec4400d8cb31a2965435d82d36653e3ae6b04d7cff',
'0xe2abe79cb86bd3092104e45b96984eb0428e3bb7c18be37b2a24b8b04e3c781c742018247335142d79e3e4a7e373f8c8',
'0x40e3d81d0fea83d540185cf4a01e07093123548ec3d661bf2f5babf60dd3f1efc4a112fb94e81a12267049d65bd3c69c',
'0xb73620631988f402f118233155ed23c2b16cd854e6d5db06d9edd3f1f9671a65d42edb74c03e72d1f218da1aedf17ae0',
'0x14f8c3ebd9453bb79d8a5e2a64d5487eda4a3454e49e081f2ed15d08fb9b1eabfae13faa2a28147185da8956a86a9f8b',
'0xa8fc1b533d8912b9e68588a13645fe93325906a34242574b29155b2abb1b70e2f11492512916d158eb7b5889d3faf510',
'0xd7f807c4bf1a83a9c0ee7a3e42f25dd857c74cd56471c260f532c8af346f3c1dc1b9209cc717f58b2d44746caea74854',
'0xcd3b3e7b4d0d805afca6eaeaa5632090952f49fa8daa27a5389a4541cefd7284e9ca80618912e05e5eaa00d7acf1614f',
'0xa8d77f147200b342c0d4d9be63d938ee59d06df02b262dc5f00e485e67399b43dcb94f1f0a924b7d4e2bd90fca0cb405',
'0x70d746f46853246a9050591fc0054f7705390cc00631813324c0be7e3b93544037bd1dee970884bb84c9797bf9dfd04e',
'0x38b711136787ebcf0f53c90bc83940e4955e5db7ff88484b492e6df9b8ab7b85188094f1eadbc853d266a04307d71f2d',
'0x2bc66b7dc280cc9f00e92736d08eccc8b2879804bb706a580b0b384fc2a2e0981706ae9889c31fb49a3a64b2dcf4c4a6',
'0x7c230ee281ff963e287b1e2cadf68afa984e6fd4f9d5a2d67fbf3eaa8ba4753564f1009e280cc320cb8b7b41b001b1a9',
'0x3fcc8ab9980fc6a8127e2efd725f11c326c3954d7e30e3cbffa2013a9dfa04d8857ffaf5c0c1eb40130bf18d085843f4',
'0x1febe30c0b9acdeb6924bdfdcdd28da1ae9fdc9b222b6248478b8b17ffb5870ddc41123d62e96b4f298f20e88238b90d',
'0xbe669beeffc8f2b18909b0b222bfce18dcde803b48547c2942c9d3d06479f90aec11c1a06e406e4e32c6ef01b493364a',
'0x86676b8e42063de2af7b172f904cd9425797619f62d8f1c679fe9af8d8ea1005937c3665c7fb312c22daaf47ebb250d9',
'0x71e58927ccb65ac47c9486f67eb2f70c8b303cf64ddd523defd43f6e39da1e2c59d2e2c836f88fce1ad689077bcd2df1',
'0xe00cb7db0b1e1ed75d435b34347a8b8a95dc861f00eacdf647a135fa6413fc1e3cde9268a7f5f8375c775c2b2b1a896a',
'0x98fe5a8f011bc3eb2f564d91c08672da286669596c3518b136adaabbc48ca631009746b4364eed88c4cf1ef1cbd24d60',
'0xda9e9392f2c26761ee820838f4ea2d467f5595c625aa170eab2267ea99c9da7dfb2e747d13d1b5dc12cf6d4b8681a2cf',
'0xffc918cc47efc152b0f543dd3c02253d058a5d75f0f1f12ac0d74b096d97d4b281ab73227ed1a48e5bc415aa62dee5ed',
'0x2c1176ad9b3b5ed32c95b3b80a1ee855520cf889bb53c0722dd1ecfc7b9654fa608b7776204701c9f668f8011137be7d',
'0xa0a3851410642a65e97d4c63ec396ceef4755cac28a3a6e6b8faf304d255f39fd73a36669a09e851d04b0b1e047968c6',
'0xb5346c1cbbf1bd14645574cec8f6596aa72988014c3f3b1242cb4eb857b0574363b46204fe5d136e0adf5e33aa006d50',
'0xd8472ab27e18211fb4279fe24102c6cae7795d6339a9865fa9519fec07ab9377649824f4e4108238d724f51a3d3f16be',
'0x142b9960e472fad5f5aa17859ccd431ae463f1902dd95c4b0515da88505911180c6eb4edbc56e85f763835be4e1fa62c',
'0x7c57261e7b8e6397d5bef5140d5c912c1f30e360f902514c900cdf1bbddf180ae5ec0b78a1f91e02eb1e20ede0e7c5bd',
'0xd27d3de0456f731ebd771a4d01511fb3befa1ac820751ad689a400b9ea2125d18365fe8eec35e618e8eb7f269cb8cc13',
'0x77fc420e68ca8dc2e9b3f0fa6f2d490f5d8ed391567bb193955f39edfde51cf087fcd82fed9eb2d5c2736ce5b16580c7',
'0x9986c5eb87eeb0ce0bce81b6ae9f8b1a78a79d22f23e16046e0a87470048c7940ca83ec9c4239525cf2cfea4127f4a35',
'0x2957cb7e50b8ae731ecea284a7f8997d98f333b151c686d7b64c8f5d9e61b426c477317095097378248e48a56b26b112',
'0xbe023920d09c862b60b0dbaef0a0931958134c707c443748fa9cc9a68cd654f36cf852d830f06c39c4cceb877546a846',
'0xed8d3c555f8d1dce7b7270120d06b21d0bcabf750663a40bf575ac7d153516824045c77865ef7ad68c67edf78df42d0d',
'0x2bdfc0121bea5ce5e3370f0c8f91defe241aaac62c6fcc501302512a686252cbafe162acccba0e2117f039107e85240b',
'0x59d4f3a417df54446ab83ebb61b937714b62b9bc7bb89b7d1d98369da96c9f557b9e7a2a617e465bfdc0d26fe7de6583',
'0x860e8087a703275bf8e32281d6ba51ab55f95bce33c9ffc5ebc24937dcd77663803224cdf88990fc05d26673f87aba3b',
'0xb4d7c2a860f44ef964a56807bd23951cdcf8d07657e80f6f81d7af520a34bd327e0cc0f48d70487a7772eacfa729edb5',
'0x0f961c2773b6dc03680a1e159d6fe3615168bf53519b14f9704db907a064e2204eb7d8679f25cb50877956fa5206fce6',
'0x483155a9b25ecd6f7c5723efdd180c3bbb5ee4651c84f1ab56b8f4d0a55683c13f61b1c36a2fccb13eac59e0a0320df2',
'0x9172ad99400006604a934914706a9e910cb929291fd530115acd0f0cfe017f56c810dcc6bd2a2ce3196bd716aa3a84d2',
'0x699ffca1b3e7141e65bef9066e263ca4eacf91846eb8d68908ee0dc1448475776abcfeeb427d6ace4060054cd8c15473',
'0x384c7a63924e1030bf080097d2ff1648d139f977cda2b31c1091b1851e509c8f56cb4197fb3d861a0eb512187ddb7f64',
'0x26dbbac3cf1eec53da9d52c0e7aee5e5f916f8e5f32243c2c9e29a90c9668d753fc03d1644c9a1b5e8bfbb28725ba3d4',
'0x25121804b9c5cd10d77465941ecb2852b7c4ea11cca7fbcd54fcfe6b1df03a777749e0ae8385aaa0608c7911e6e28be7',
'0xf2b0c3acf4a51462e129df6682fcfc16be26f235caee82d8d0d4312b82147d79ce01d02c99a6a1e6e57668efec64294b',
'0xe42b0d857fd063a5c8e65f9851bd91b1063fcae8737c1624d2a0a5b32210ffa729fdbfa9703b6aa0518ebc66edd07e98',
'0x7df0c36ccbbbee2668967346f762cbd2a268fc7b7710b5e025aa473ca084f8fc5ae832cf65c836087904ceb3d5efd45d',
'0x4ad3361f37707d2387d85fd89fea287ed7f175ed2485f8538d052065fd5db3e25ded71754f42906b6b8102828b7759f6',
'0xfc77c037bf9b0e7db696769bac15c137092054ae3945415716c5b77d0a6d3ad30e10f63afe16c69bc0f67453c4a943bd',
'0x838e0da7f3503cc65fdfe0b05a1d457408e6e5d295e524cf31aba1cf33726c8c213465a398d24d8300737753ae8f54fc',
'0xd0f600fa9b6f6a4bb92ba7404a83db461e1282e7e7be9e4ea07f956424a5d5a6cbb78a0681aab4f3517179bcf84d7322',
'0x55a41707e80908b135019993e45338056d625f216bdf75cd39918125efc54bbafbace7b80b75827be4b85d84088b5a15',
'0x1cc0cd0f980618b74d5b3878e9380adfa5002b7e4465eb4636671210060c63a10197c4447e36cbbda58f0ccfb012da9b',
'0xa9f9ae911afd672ae1409ecbef73558b93dad5985d109204a3b0b0e1331bd874e0ca9aaaaf02116cd2c18359df9db02c',
'0x3a5c386a3aa8546a1e6fa65fffa26c7338f3e48031b3beb2a0c8f973b62503fec158587f088beca067b59d1a14b0c891',
'0x43e56c27c064f765dd20dfa003862e2521535cb51909017682fd461a2b643239d34b44a0dc8a1d56503a62ce6dc45419',
'0x75b486f2d02eeebf9ac1c52835c0d5fb26fa3cc969cc16959a9c00314accacc10ad664da63e3c3e3fb55e82739620513',
'0x3bdb3c57f67f676b3a4756bea1cc9b0e5634024f0e8ebc00a5fcb55d4ca03958609f36b83875dd160c27712fb63c1c71',
'0x8f32dd917c22cefb8cae97595c09e7c9962f22917d1b8da843c990d3bbeb2736c4281d5dd02af9a323b23e2b024810de',
'0xf21444bf59c4550518c27dc64038c71775f4fdc60dc79b6a223cb6c518234f1b0d8bdb445f1a21e7e42d44091e4b9cfb',
'0x10fe88106e45acf6365aceff1cc2e49e4d1259f3f9b7c70e226d0ec8b131909023c49ee7e82bee0d47c7f314c9d433ee',
'0x1b7b5e14eea03ec4e51be1abb1b865b28efd6564d1859699dfdd72b52395db9d1af9a57793e404502c54ca591aed1d1c',
'0x50783a3675e8bbb70d176a1d6c1bf8819b11c09e13639eba2a79ff2e2bffcfbba929b0c6f0d83aec2e0b89f35ba1003d',
'0x2a32b2810698cd6d1004b44b924bb53716ff04af0a140eb601419bc65ee0124ddaee12ca6f8b4c53ccd781bb84053207',
'0x126ed9643563f06f3fddb0187417fbafc79ebfc569caa77ca2b374016822f1bc81a35fc21d0439cac967bf4f99a599c7',
'0x37c08cc3361e7d5c31fd0b0f857bccac0f0c9793789dfa7102a3ba2e5d70b9bf2fbc0637aa78638da6e8351b0cae63e0',
'0xac9a11bb0ad63b4d25784ea023b63538deea846e5ec8decbf2c8a0e2207d9af1bd89c3d4bed946e7095b54db33685b65',
'0x017b90458dc192bf3c9c7e3441ac3dab9f0c4ea66a255faf28b21752cc0ddd26ba8ae38e34c721ab81f178027d7c3d63',
'0x7609b8fc0970e2b307d8a9b90c6156c59d4b65e99eb053705be89cbbee10a707a604d5e34896a16298aef731bf02b194',
'0x296d8d22317dddd5401dd3c56a5ad90699e91d5ac3d1c66d7cd4046221b7af869ba1133e9e2eaa60d05640985c1bb182',
'0x5f87c6b9aeb95b5b13b404c3ad58b90336d52682254e6a62ebbce0347e7c10d0290bfdc24639e71573dbdd87bd3e5e36',
'0xbcf225037d9b75cbf41b3560217e6208f414e6e2ff03e404cdf4d83ff2f1b396c7a7b8b3db099dd924fcb6da9f78410a',
'0x6b84592cb5eed444d2c7ae394fda7605738094eb3afdfb2f4a0f528026b10b876d67312a7b559dd4e14d1b0b884a47b0',
'0xdc5d33d17c90f4b3dedc34ee394aa2d69933d46205a2f308f3d327f79b781c258e07a8e67ee1adceb132f5ffef436d6b',
'0x24704dbf7a8c3c2d10bcf03153c666e04a23a93fe3606303c2a0360be12f33c22c05e1d0740a29efcde4b1f210c19e75',
'0xd7c76d7b8c681d7a6f69b0ae8a3af718037b957d2d199eea91c0b7770bfb4253b013d4af7b26625712502f033faf9cbb',
'0x2ae458d6af6e221f1726193fc906572de9a5e62655641a1c8d062d7f8e41beb9f3dcd22b053e7be026385d7c25f1216f',
'0x63764ca77f1dd9cc834d956acdffad9824cad3b7e1c669c251b419f6e24a3500976c99d08485d4ac090700e052357751',
'0xf6edbf68a6a1d646a86729d1c421a9f88339b7da5c49b79311b31610a4ec478fdb1c870936b7af4b3b77690c8c528d97',
'0x2a7358a07afa953b2373a7add9204436369f2d2fd8dbc5a2571df73a8e0d4e644e5a0424d24bcf3b2014289deb1651f7',
'0x03871c498a5203a0a5e98f1085bb5b389eb08858876ad229e5b4af71c76fb330bbf122cb5432d046ddbf2ecaf1124837',
'0xa3afb3ee8f2b0d313b71102c1aecc4e1c3fe0450b4ace554a9631dcafc8938900b3587656345d9849c078b4085ae294f',
'0xaa1cab16e87d4dd430ee888a2becb3352eaa2be377ec4105b8f7d7945640d1bf1ec8f463507231c9e407d5ebcabb4730',
'0xd4bcb1f91d936d2023da6aad0c51a05a8a2c367bc60cbef5458c01d16b20a68d386d36cda884481cbc05af27bb6c62f9',
'0x5fbc219743701e2f3c21900944f4417b69fe74b080b37db8d9016e42cb0d022af70347675d3b6f23feea94a68ca23f49',
'0x3d2488a827ad538ca294d44a83fd1feab8cc5d7e3ba46ad33dae8667b2d0157821245f0f89cdd152378419dbf8727ec0',
'0xa1946c2bdc6e3d2496b21724ef12b86d0331a2af49c8b357ef4f84ac72f6cfd01dc160b993570f36f0e74df4b5e17333',
'0x2aea4ab40b416aea0a641b7e96d409d0d608f2dc95175008266389b4d3bdcbac3429e46326de854f2647d3a95c032a2a',
'0x2d5ba0fec08041b68dea146ad17dfd61b8334998453a82283d21e421ea9d0315faa86ae5aff574da2ebe7b7d8b89dda1',
'0x4334ea7a3765e435c3455cdc0ed0effba8bf075f65423a8f020a556d065fad3fa5a53de475dcef2f4db9cc3ecead999d',
'0x06a77efae1de753ed2dabf1dc80ede4f6279b446b3c213d186028b0326a6abdc76933abd6c54b37c8a42821dcd9c543d',
'0xc55db97fcbf19daac8088f3bfa9fd1632fa6728262e03bd4a4ac2606988a60e0c3fb6eb1bfd8ce038d8bc103cc050789',
'0x13c9b4cfe45434755be06ae673cdf561aa24b6cec3d2a18b8b2fe4c17760db9482818bff05fb0f166576ab52006ce33a',
'0x07d059982489f7b5374a0a07eaecdc5c355eeb719afb01dee8da0bfc9f566721a7c0496096fb74895e3e769f48beb6fb',
'0x8f2cc445b4b37ca434fa47575d9fb12853672c9beea372f7f70aacd5f40c4154d738cad4d849b2e3379dbb588bf89400',
'0x7246d3cb5497210bd6d3d3eb1ea6a9710333b9474e91b357de5751b3d698a40b82268fd81e2c0ff5adaf000040094e21',
'0x421ac38ff59d6858ed843a665cfa4646a8cd015a1a0e32845f8c2b90325f54fc135880e6a4db67c85a4636cfa565d77d',
'0x9d94e5c929d12409e7fb391fc4e1781c02846d3cd839208acf6dc626caa37df26efa527abde55a85ade78ffa94affc85',
'0x4cc7363775a28527cdba87793da2d09bdbec3bbafff10e102b536c0a76ae4c82acace68030e44fb757672538dfa22038',
'0x400c7e8f32db574cc2c206176efd785aa100dddde1a49217c1bd509488ccafca60b444f027d0d1c9b2cacd73eb6c60fa',
'0xc99f2dabe523f5e23fd503d8a7bbdb8a1de354801e2e105d2d5fe29e7545677049737f4705834354eec882f43f2defa7',
'0xdfc265d28367b243e0a85d8e9539fd97f7b3851c391c68df849f103063e7609a8ba51b3b3bddf8468341d2f43666897a',
'0x8bb03d4e6bad3d19bb6fea035020360abf3a4d7d92857e7b795b3c132a8af657e10b456f6a9bd444197048fd7d4e757e',
'0x863ec795e919088c98d1d9bf0021aca7e9ea6cd51059c577ac1aaa160b4f5cbce27c971885c84b0b7a99c38ac84c72dc',
'0x820a748f0bccc38c770cb2d76bf114058e54c6e96b4af849232ee0b404c28068b716ec7758276a4d0ce32c0718700fbb',
'0x8da9b31ee0036ee9b0bc51f14f385546b5973c480889fb00bf2c03dbe65bc843acd638994e7ed7997537c9b9067b74ad',
'0xf855bf2d9dea7816d9f23378d898b06156ed1149e5e23aa2b7caa18285de4aa759d4c9bbd924efba0be0bf1340db4580',
'0x1ff518242777abfcb578168ba4bd87011bf348490bbf71bd063ed3eebb5c3a4874dbdf5d385fbad8565aedd582d27e06',
'0xe497e07be8414fbc41e995810918adfefdbc956f1b43dbca12f104271958c245c2ba96cbab37958fcb0bc9a400597874',
'0x15ddad87af72df03e6c94dc9107a42a44031b4e9a059eae61d7459a44093bd10a3f392ee8883e97080c02762e592bada',
'0x5545e74a2ecbcd18081952c3b91f5cb71e2d09d61b7125ac4fad5661b76098a035a4e8438cb0b05026289232ffc03dcc',
'0xc7844f43f672a96495773255c5b1beb72875ba004f86a763497ecf717bff0304f9e7223f2d57aea6e62a7f9870eee45d',
'0x9646caf776c9a7486a3582b91275833efb5258212cf3da661f25e6c6a117be56df23515b40f37c797a7e4c32404834a5',
'0xcdc55f79e5a40a7e326635a7394f56608d6dba0f85e4991261a7510a5577679048fb6602dabe176b8e56e306d3b03c57',
'0x503f36caa69dd1a7e7173beca6cc5cb45e1dca8a2431ef7ea04420d933453fa023a57ae65ff65e8a4dbce3a676a1efd2',
'0x4bc46fbbb8fe723852e1757a20a2598a766fdb07376000717b5821e0272487178416a2af2bc3d7419e7a2cc4e3fe1f56',
'0xa38a2129c03a5344aa3a24533ae43db4686ddfa51215c9d388b8217005b1821730c651dbad4f4a7c6048a9cbbc6ac763',
'0x6320d89b11869d058250fc044c1694977cdb36b2dd38ed9e7d3bda3229a403e80cdfc1d9c4d7d697fed72a4d8e8e6102',
'0x1644353fe3d69d0bb0e6487496b4cfa5dff3cad85034f9a7281e58437c75b66ca78c7d4152a9644c3450e1992c7f0727',
'0x8e113f8b7b6047290773b7187b6e6b451963fd59bb367f9c1eb9b05d7a767d98679e5e1f55255f081310049a6b821818',
'0x906e9d7f308e6bb448602179734f3270b55181ca9ec1fdc368c806bee1371511f57a4652b50a33fdad3fb5e658d31adb',
'0xb4e71b824d4fbbb8bc3d3505a0cebb2d288c63794cc21c66046ef5b12f110bcef65d7958c5e53c3215ec8efd6655f037',
'0x964206558a1764a420d4a80026727d744392fba4ad0bc9b8436f2ae2f2775790c196f7f0772aa91ec239e44731546b16',
'0xd74242aff96005d4bf676298ee0886e75c2d96e68179e5f4b05280b39b2508a489dcdc3e0c537810a2c107d2238b924e',
'0x7d944ea810e73ce25cb1772e3a80ea273c362c8a0a6d1897eeae7d82435f5ff3bc011c1cb53b410be643bde4660faf3f',
'0xdfe223a44d60cfd15e28f63dbfbbc7701c450c69f7efedf9271670cb2ea35593b3ac6ec545694c8d40f84990238ffcd5',
'0x25b69ce46ea9d300398f43bc495f4f0a48c55c078f3b8686736bbea81c02096bfd70894a0c591c8e40a392bba96689a2',
'0xe58aafd1417d956883f1727333a3a72e690f7c9ad4e03b3a04b9ce3723700fccaced15214874688c3d07735157ab13c0',
'0x5a991fe54132471fab7f81fbbddfd5e8cffb120140fcf768fc97813dd83ac3a8e182ce598a4fe9363a9e299e84c82460',
'0xebaec3b4da54ac8fec2cd76e4377fa241e671491865d886732b088ad770cbf61ace5f8080c3161dbc3ed5c06650fabfb',
'0xeb27fdb78b84a5af5a1adbbc49e366013286f66a69d3d6bf625cdae80ac4650a7f9d244a9a21f80860cce184fbacde37',
'0x974831ff986c5f8c75ddf0737b7a7134c643c8f214f65a1b00149f1f0d266765b2f76a7932a295968ddc287165c1b7b2',
'0xb37e4db6930b28225da5e3c0444e0e7ec0247c8a3e9f0ea5ebb47c91b235bc5a548a29c96c7ce66329ec13e002278e44',
'0xee6811a578fc62bd0cca3e307a63c53d216f64f38a386711210eb69b60e321cb5fbff10ba6befb3e50c0d9c16013134a',
'0x08e187c6d9dac423744b99954d5a22ed697db0d1eb3187b3e69663c91c5d431ee21cdf1449ed3d35913efaf0eb9da956',
'0xf0a71a7b79eb903e163f72ab1c39c85182c56d03de4c48090eba5362fc56e571c0173099cfebfc9dc2c6acdd23477913',
'0x024e3f0c7d30311151885c00b5e75b0cac9e2aeb4689a72dd7b5234c837422cf7ddc1fb4898123d32d611d10c8ad9a1f',
'0x6d615c396c4a20659d9a8175becc9888ca1a0adaa3d5a1b7291df31a34affde97bcbeeca74afb3778a64ae9f6330fac7',
'0xc6e4fc047c72fbd1ed7c0ba60ea251468683eb9b2812ee07143d138397ce19f9629cb24473a1bc1742f8178fd2806177',
'0x1affe77868fc008142d7780a7e9af90f34cf180a691c57bb0ad14e2150c9399634710c7a1a272e337b7619595463195f',
'0xc6a3aa0a756bde8f638e61d67502f41c00477504e74f45f2c7a155be178fd45c7040caa68ae87eaa03440e38c54ddd69',
'0x794dddf0dc03523ec14ea2b32f8971a637f1a3f0cfbffd39ffca0cc58858c980b210e4b87da70a364d263ddd104f6939',
'0x84a4969ca14742b3655c229b91ace6a932fd91fddd976daebf77d30c3a175f45bbc2101af67cd6ac885b83dbfcc6723e',
'0xbb07cfa438349d87933da6c7b2e552f7b89b2f2f9b561d858ee4494b0d1acedf19fc3273899279fd84776b7f854d06b3',
'0xe69e0d5cdf608e791d23e4474b818621502acdcf73f6f9c2fcd71d56bbf51cac742e36649e12c3627bc47740c3cbc251',
'0x331b908d0d69d85cb518bb4f6c77f725f0ee21e93fcc28698338ce75d9705d2a7e838c6f6e7f5efd0096c797ca74221e',
'0x9c4b1fec3b69eb05c1c656f25be91c306b4635bdd9e7424df22b6b2abfa3f055e08f74a8d0b2294dab894a6efd3044a4',
'0x6d8a9f89ba59fbf836c0d75cb403dd17a86b11964eff549537efa72d729f96def211e46e499345c3150a8896d8b6f27b',
'0x3c86ef331e3281b885c98ace7a49915a2a43193ce78054e7f2a5f28a06d2c947d0eae450f80bbc67add98f14e2dc03ca',
'0xda7e9f13076067b901e879d3536d6366946b485016b8d349e14d642659ac3b540c1dff1ec80677e5fe27775da140616b',
'0x683cec8de8e0df8497514a869862373a59412c376186f0e62df888ee500cb5b6a39e20f7825547d6e20ac47cd30d2d44',
'0x08080831ef1fefcd2d220802e56a31863e98f8023a16fe0ba5db3fc246747aa8c3977c240aaa4b9d4c02ea3fc90455b1',
'0xdaba62aa02851197e5d6cd11ac2f0b792045663bffc02e3a061724c6b6a9f22f49d4911b5d6df655d433649e534e23f0',
'0xf551bf06cd06cfc5323ab2e855ed05bbca07893e5b046c9f8919430ee47504e914307a76411a0e6a0747f443013eede9',
'0xacfde6359e7c09bb903208f540304f3f2277ecc13a02445bda1004b63f30853e32206ccc491ecce4cbb10a90416fd321',
'0xfc44b4e0c826390c04e3b6d9ea730fa0458e56ea000c4706cda362d7302d0d30e8b76f7459cb05ef52aa1ab675b208cc',
'0x2c1e68b1585fca6cc0fa39519ae9ee9c6ba4bba439b9780c9a9109be45da6450cf3b351b2c119194be09ceba6a220267',
'0x4f89f21dd64f8d20320ce13f12d9e43d41ab538292a4b6c4c206793c2d4c79abbeab3add99e274e75da824cd744f59f8',
'0x8c0c38a204045a8c22db94c83357b6169c3fb8360582fe87799dd2c43bc6e6c041cedc9b30f6c462a92e290bbbe94f0a',
'0x17e912e671fce67411dd93732bfcbb96b1b753fdf713234096a4e15c8c33c2ecc11c049046866c1470bd6bdef3342134',
'0xb12964e5d7078d564f76ec4565b50059d50344096b429d842f5f6659c6e310b85592d066c866a51a6b5bb5fef83db50e',
'0x265ebb4e959a795614cb1026b90c8a7973527b98f7556acbc02997f9a663621caf27fa5484beb7f64a8ed931568191d6',
'0xa6ea09574d8585de8b2a639d4b9abc696ddab87da701d1c0b38724cdd6cc0e5ff8a5be83d16ca0a4e5118516de4cedf7',
'0xe931015db885d88848819a2b610c888a77d65473d9b85480cefc8ff5056b94cbf02f4d3027f4c98acf763e22be1cb1e3',
'0x9f6eed85f97aefe63d9ec2c2e7548e93714f87aee0e1f3ebe6ad7e4dba80b3bbeb185d93021b88dc5ae613dca5b3fc0f',
'0xc3a8005c2ec795c48d6f0d67d2b2e8553d911a87165e096a305c6ed49a3da31a5909847db97962eda420e62376bb6b0c',
'0x30df7019c02315ef42467bd11077dab568c77fc4b92cae118afa6dbae5d42ee1112dffae1558b29aa5506ec41ab922dc',
'0x54db03f2ec6acde24e4979d6c17b0cbc3da3e2e4cf0157e876b9fee4cbd5063aac70d37ac98fdfb6c63b67a3e6a956d8',
'0xa172e8634e3da8f8d11eae85ff7a39e4894288e766e577b538153bd35afddefeb14ec4724c6924ed5f92c2c26efce72a',
'0x416a95eab5f928120d1c8e7d52706adcaf3e240c940e2641a0c48c31bcaa9bd22e2d6bac3f3f1dbcdfce77ee24b5c48a',
'0x89255134349741a7c2e1fb2447d6f52659e9ff6dc701c8459e1cf0201aa0441ac64d61b40ea74f0c3372de2d9663e8e5',
'0x31164bfa0f02dbd8b2004857dec91f764565fe955af8e69e1986dd4be388fb554ef37f176883581a13203e2ad6d117e6',
'0x9f7759b59087242a20b8f766da3c04606f0a81f3cff836bce36eebef1918dfa261c575adca1fd7cdb936ab054afbb81d',
'0x218514b7a232a1049478918e531585c21a9a6b5262e49ac7eb5ce727e26a8d05d8196a1767f4452482580b48cff467cb',
'0xfdac729315156fddd945b4f81f66f2b91780762eb08b506997f7aaeb6fdc961b4006afa8a86d22141ba7d46b207a75a7',
'0xd0ef9a03913e8aa968d12ce1c354a091c1c5408983a135acddc5bbed59032f10102cda0db49a1226a63a06e2bd5feb98',
'0x99f422e24bb91193bf8b33d02ec24c6d090367161f59ae44c9152bf8b8c4e90339ec8398199197bca82435537c811ac3',
'0x022b518cbf137d3d613e24d056397a3527c51e3d8695c219b8ec2b8c6647988561c9abbe8354e91451f618121943d78b',
'0x609fcf61fe64cc755ce47164dbabd0f499747b50bf324766c498a556c87c6e0c0f0a34a8d6b49b2f11b47d762ad26928',
'0x7110aa0cc52b286bcd988ade8d910d310eb46de3be6bdb26b64af2cda5fca36c6c8f6ca84b10cd68535e801e1e59d511',
'0x8ab7ead298421ae358fe6b0640843ba91bec1ccd89d47d9066307547e793dc7b98b832d1e3ae19dd2c53e488149dbcb9',
'0xee8ff73da7ccd3863d0daa7c7d8fba6426adbae46eeea4fe5c635e75572199a9c0faa90e67f7baee77f260e0c9eec3c5',
'0x594e4cf4560a25b89b9825209b16f0894549a740beca4a430442c8e73ef06f88d75fbc33d6b24e6e6412cb570b6cfff2',
'0x20e85d1d0f1dc5ae0c47160ef1318569d77254a85e7a7a9749040b3eb779566069eb15ce0d76ce3cf8255996039822dc',
'0x8b35c1dbca8077036d307ecee0daa8d65f1b4adb2fbcec7063b6d116b2d6f46145a71f0f7d80f3951e3d1608c4ba1a57',
'0xd1e806b515d77cfd08b9fadcc88acf2899795153ea1f1db3a38a0585d69a4b0f986d8de3f608e557f369d4ad1f7d9459',
'0x0749f53b524c5132a60358dfa622834136bbc971790a44147fbaf1dc6e893dffbbbf6b1dab3bde1efb2f81add38e1ca2',
'0x107acafb7df4bafcdd5b6350e8c9513c845d8394e3fc0a7530a72799bfdf555acfcfe60fe41d755d7c5eb94da8f635a6',
'0x0ba7e553fb2ffd1ec3715245d6248a58779cb16699191a50384f8979a728f85cda13f943053dc9e1905d9985ece51b35',
'0x1cbaf6cf59b8f5a327e1e353d41f298e796d9b2217361681ad2934cf437c77643296557538b8a857aa8e77f88275b7c4',
'0xcf383b94aac101689d029285d1917f0d2c00a7363569a6d72e448de7864a20bfcda24f35556f778cfdc118310be16382',
'0xec45bd3d520c9c98316fa3a113e6c1359bfb878e2af8d949a9fe6ee639768e4ac1d52431ca7af214f291362535fe6ac2',
'0x30d45419e2e7d89eb8821233cda30de1418512c6cf3a7e7c5fbe95b13a1389024b199a22565f517dcf38012af4efe03b',
'0x49a2c889b7ab4f4bf6a845c0cda8428bf4fd84cd62351ff62a569f270bdfbd92bf1c542a5bbc593cad36fcd0af4b7921',
'0x8da94c2c214913182995819d6d99276cf4d1f517608a3cf3b4e003786153b672b9835dfd6283a361df05c619eb5cc954',
'0xdd5e3846c97bd5133934a676cc179e3b657fb03f86b8712f5b57b4a78ceb8ce02c1b19f82574625975cc109785d0eb70',
'0xf4387ec0f0c5eb60c296c98e5bd977e437232888179608e3380fbc369258e1b647ec033f411d37e787e6a5b1c57e72b1',
'0x6234591a43405d6e411094251e6a68cd17c590f9a5ba40ff4ac46349d8848820206b604e44f36ac538757819ffb84e0c',
'0x470c2d569dbd6318446d4b2acf6f05a031021da2759df4a38ff8aad6c0ab162a6ffb21de356a6ab98fd932c8c8420b5e',
'0x7b15faf786d645c349704232ebca1abb9e2e8549131fed39d60f0d7ef65b53c4c8e0a7bac9f00cf1af5c8b697c1caca2',
'0x6355c7e54efcef7b5b4724c6b1c6f6dba4df3e8ad04928639756833d62f88aeb3928226c83473963bdd0a8088673c706',
'0xf2a07056e6282cea9143037ba30a6a12d4570ccb108682118b8f6455ce36cc2271a6e3cb18ff491f9e15bc013065fbcc',
'0x374a7ed6981c9154baba4ad44345c253c2630111c3d6f4b9bb9390c16e48c8c02e9ffb3f43395cc2f41494b11e3f7476',
'0x0f0cb19278c90c5c15ede3660e12b46aef12c4f70c428681c3cc2aa2edaa56c282cba1fa0eecff104a8c73c83b260122',
'0x52e55f4d08cfa146765577d88be58708a2d93cc7a09de0c64679d8c53e96bd72309617a59af9f86d5e2d92c47922f3e1',
'0x4fd04e0a3eaaedd46f74c41ad35de07bfe1783dae4c9bced62c0138e4fb5142798b1696079faffa6d80aaccb96585662',
'0xfa8f99e41165540e485e932ce43b73d9807f89715a6d0d6bd6804bec1ba526fcf961fa46154085cc3f7e93baaff0bbaf',
'0x0bcaa75a91a42c68a3345acc077b3564870ab76333032faebd293bf995f441ff87082765c608768c5267abca131ef4a2',
'0xbed9df701ae51dd635ab7c21f7d3f730a4683f503f5b39f02ddb839540dbb4063a556ca193e4286c7d27ca5e85ecc425',
'0xdeff1344b427445a61804f0961c579a60853fcfd3ec48df0781bff28e0d7ca8fdc29f50be0dd310ae82f2b28273a02ed',
'0x2132991a447f7c18e640e90d6b696dc52f41d15d5511205f4c6224eb5bb9578500a14d6b2ddf798e1637cf721ce447ef',
'0x000e1e74423a509abfaa438672fa1aefb16e4960d677bd5b8e3bfb892e9442d45547f81363c80bd59cc8bfe749ab30fb',
'0x961610c13d956c02491e051a702a106e00f56f166679c5724ce65beac312912862428429aff6b80ee1ed91b44ffe1672',
'0x658601e898f4a44357cb18f4cc793179a9c60354f2e114dc56ff8ab26417006c8a7b2ab1b07266c5180948f151d1220b',
'0xd15925a2ad2407204f52ebbbb8a5ea15090c4d041ca2a85470be396e5bc00d78eafefd50b87b97449e25fb4f6ef5f2fa',
'0x155a21a0ea1b3bf6907a543b0ec63a26f349ab81f9e19a864f5798396f4e2ed47debcf8f25f5b8769bc17b77fcc0ee3e',
'0x59c42356b421b3817cecd618040733adaec3ab7e0e7b05b37dbe510986cde92bbe97c2ea7067513f096ec5a82f3a76cd',
'0x22023c8113ccbce6275985489c4b19e387ec6bc98a316b1c5d285282c7eae42492651add6e6e805edeca1fab01796513',
'0x211bf496409cea0fba17eefddc004a51ecc4aad77c7a8dc50221623da9e8c6d1f835e1bf566e07bfce3b37e0dc7f1bdf',
'0xae89cd09026ecbda7de8e446c8a27ad3b275581f52ec2c454c43d7d256369470430d253d4e9ac448fde8465401a37448',
'0x0d3e1f4d3fbe98dedf1e514566560e054b88ba1112f8361052ed54f98acac43e9d43141b77e26e353592871a0660779a',
'0x17d2c85b27a2db7beb61204dfd39e0a165578f1d4a9fc0af5c56a0e3a096cd1b26d4286f4bbd108a494915d3fe584852',
'0x2a276531d782fa7262fff0a7f7b749d37da43918df6ae692bc97b3872610a2fe5fa197d42ff37f56cbed97416cd63317',
'0xdad4fbee3c61f8dd10552c6957a93ff721e50d257bfe63606883a16e8eb150f271468b4bd388e4502228f09516775693',
'0x19183ab2203356ca8fffc28822743806e7227326053408003d31a0e2ebca5f90352da96cb6c6dc92c003e0511cb21d58',
'0x3b5db6cc158db1c10c037272a87dfba3dedd0d3a9733b9e4e7a00bee88afe2c5e22a9f271c923fc11e87be8eeed1d3a2',
'0x22c29e9a46ef12e6f61ec085087dc76f5f1d8b463450af7a817b16e5337d1976c1fd9156a40872d3de1bcd3f6397d957',
'0x32133e27619fa25feaf4a83217313f07c5604aaefc1d8789a07c743ef4b5d390469c3468e2398b3ab361b171ced24621',
'0xc5edd3aff7ac671c2fb9dc2ae64d70ecc6f90273040ba71f9d248d6def6a17c8e867dbc2b689edb08436b26c9be427a3',
'0x14765b691d789a1e4d2bf9433b26440af0b54ba7d0418b7c5ca33473d49ad82379bebb42429c48eb5d12e59b0458841e',
'0xa04d54bf0d3b3b9d6d9206280e12ff8d211b7c674e7fc1289a9e8ff9aac9796bc7307252b76e7865c2105327d98bb333',
'0x6f39f30a65c7c58c0cceb75587246212271bc49bca0dc4bcaaccc3e4e9ba2281de556ae11dffa475573fc7c36feae52b',
'0xd9a48ace044f8e804fea923bec39233b241aa53ac081d63eb46cde0eda11541a51c7cf0d5224fe6b4acc62ddb9088e87',
'0x2315582a5269e21c3b03673336cfb30dfccbd1574ca3545de23a7f53dcd8bd1612991ce34f3176feba2f0c8f64558ad2',
'0xe8153812d73065fed93192ba77be7ab64b12d99d20e1288ee516a22712c90dc33714a6f20c46e24385aa0a2023567fbb',
'0xc90981f587234defa8efc89a9a9de813316137ee2a3bfcfb590485ef46e9e329494771f042249687ea50a1f045e782cf',
'0xfb326c70812895be5c14fb67a6cd95cbc2b16a60bb3b1bd1d6c624205ff42204633fbcdeb5da49e861c37e4d8c21daf0',
'0x63107cba4a8f68731be722e133dc36861c8bc3662d2315ea9d7c055c5ded45c5fd05482703733247e50ba021d0021c6b',
'0x73121d6315bb6d59b6255c632021adcf5d704238699575a829fe13919b65a1573f864820fec549fc1bd3a4efecaa15d0',
'0x6ee21d2228c1718e9d74b22664888a20a52c04176fd74f5aa53b80f042216672fe020b95cd6fa3c496f611c7611df643',
'0x371b4ecebcd293d5a84968c5fabaf1f14451023748e81adc6b102676adffb093b187756831043352456c22ce7dfcfbcf',
'0xfbc037d7d3e30e54cbf9ead4d1a6b3246ddd2e955721baefb2330b4eaddfb5eae97922d348768c89662e7db5777d7203',
'0xda4f2d8150b69d04290e77f64d1c74cb12d3e45ee8a91f5465b5a711220e5c95a3bf3ebf5cce5dee9bcc92aca54068d1',
'0x950b48b5d47238e862a7c03dccec307ad207b501802a7cb8177c9f66a3325fd15ca20c7290be8bcbb2ec80853ac50d51',
'0x4d57e0f621b6a87750a418d55172850d402cfaab82ca5dd749e833d65abb2a410c1477b56a647398434e5a19bc45714d',
'0xd7854cdf764adb7f016657bf341eefad9cee7cff98abd6249daa7e7615d9bf8408cdb7e61bf519b5ab5881a552e95b86',
'0xe82a5c87b4d122aea0c5b744fe15a654617a5768738889dc14d6e92bd557f6d0eabe742432ec8553c35e43573431c175',
'0xd049b98a25fcef1d3636a51097d6e3af13b24512f7d9d9e90421957a62147e037b250d437f6e404d02178ccb5f2a485c',
'0xcbf4e8365f909b4923b3f0c1cabdbf1c77ddbe0995fc63aaf97d8f97c15e85256fefb34b1398f109fca96c7f82f491af',
'0xc88d85eae7573208238231fafb251e0ee8633f92a6e64add560d5eda218dd205d8b72149406accc4f32afc0c4c86eb4b',
'0x7403690c5e205a15ddf4adf8b2fc9b5c8ab9507a04a13badcb138ee142bf9f085bfae869b01ce8911f93b0075c1bfb3a',
'0x47f8867c9862c9bc713c33307f74b423267cdf8931ccc043ed9de1c0a078f51e91009418792caef237779922348326d3',
'0x97f8030db16c4b9772a1686ada8a727e853d9e39b6c3e55857a8a1d9d27732726ababbd52d200f5b5d8a4adb034c1cd3',
'0xf605ee69035980c8590f8e6586f81a9fd1bbe523c9bd7512ecb4d62f81057945ffa0b8c94090f1d145b7b69e76a69b1b',
'0x7b22a153df71e12424063f53c49536f08540f17044fe14263a725a5d21b7fda4a5af5232fe6a9433e837fdbb21cab3d8',
'0x4eb6dc54bbbdb4043f2c77cc105aadbf286bd1d61e6df991e1e16c0a568bbdcc676e3764ab61e24dedb3a58adbdd09c3',
'0xe9467594dc74c374ea1e0986b6108bb737f52197491fa31bc616a627d17a54e4cd4a30b2d069ce5017e12a6d95936590',
'0x4cb4f31cb24b1450f5e912d712262a9380e16570ab6b07d7eab1a5328bf913976d6f37d32483aeffa1e8fd45abcd7c62',
'0x975581172fb95fd967ccf3c367ccf90f9c216f9acbddecf03372d2043ecce0b4f478077ee5960a6cbda28dc6fb5fde03',
'0xa99f1e2c721b8929f108f72905b85fdfc58e5cfcb61e263c22ed82b2a0701fb335438eda3df7c3abbd2a2a520321663d',
'0xba16bc25756f66f6b2c98851eec7cd7cd0a0624f49d1ef12d03164a2a4204da4576f0fc02f2700016dab8851f598ec99',
'0xa6dbd9e5eb37e516f21fe77bd84073e9fa9b396a6c26b2a10de6b66d6d04e8e2ea37d32692c569cd285a84e16e591dd9',
'0xcd8b8bb0c7271f0f93ba8d5bdbe802649b015f00332a4a991c97f71f6ba3ee57cb4ce302bff0fe16cf3a221b7cc2ea8e',
'0xd286bf25227f88a633ee1a07b9faaf69b568dbb16bd35cdcb64d268bb833be54532afd680f0c830471d01cafe8717721',
'0x5a63a78c71fec91de25aad70ed020b6aff0999637352fb1637ce9c6a1826f8a378fd53ba3b5900ba2ba341fc005f5763',
'0x62549f5fbd3d764ec0cfff60a50696c582b21efb9f7a671f85a326349d9e2abc139c367cb96595d3ecbcfcfc6f90c5fd',
'0x087954276820316658d18b05116f17857e38ab2d8350a41355db18519f6a713447f687073b7f458dee9bd5ecd28fb606',
'0x1c19d4cb7ab04f5f7c92764223f817e1ec359ee31b8aea053393d1d7e92c8cf4b6188c17a3ae4310295e25ebb3b08012',
'0xf0bf5a0f5dce07a65080c961fe8da06ffc989e1522abb445873f0eb6c61e7d3ad16263a33ce791e0546a8f65c81a6157',
'0x823fc02adfe644107efbc3ff29652e09e8d313326bc196170b5005a9249a55d0210492b96d922e0061930be2d23d7a42',
'0x49796b6541c706ecd4637aaae435cda6f7d05d99ee0535c93d0c4a526fb9a4d4fe72ad5f8806376b12e4878c3f2bdf38',
'0x9a64f4aaac4f274684162bd473d608a4381d751e49e470236137d638bfca767ff199c671f6644fb8078e9fe145a1fe38',
'0xb94e2edcdb419978217a8827b63656525a423fe50da70d45d84d3d571523e5b0413dbb18852bd590f7f33b9580bc0b75',
'0xee7aa94c994da053a501a07751fa3a001beba3017b3fd117d8538ac94279d31b27d912bd9ae98a95bc419c77644a9fd9',
'0x65815518aaa2c2f06381b6fe7d0990a99acdbb774772445881f1c1414a63311abd11905fe749de95bca88933940aebb4',
'0xe06d5be7c17baaca82d200c7d56952c0b4f47128ff82e06c24bbfdc237d10e65d89e6c4ca53130e0c9808c38f77ea127',
'0xf7db7e4ddbcef0d2b235c69f1ac6ffa04a6eeb0b47fbcd3b57e53f9ff4e12effec6321fbfd49843555430e9fb95fdf69',
'0xc1cde0731cbe07db119dcf371174e19f0c1afa16cbac9f1caa48ead26c59be3c32a8399546c38eda2a8b03fcc3c916af',
'0xb60337f241a58644cede883e29e00f5e84fe4c81de53b7e0306c5bb04de1b0226a252a4a049201e455cffe6537ff8f37',
'0x7392d5323285a9d444ac735d6ba57145efa5ac3c2d95dfaaa08edeb1a83aa1434871faab1be9cb78ca0f4538002d5c74',
'0xe83e770ea72c71123f97df7585612ff58f7eff9f20a016968ab513d5ae6aa9cb6d2a503ca2b261ccacf99ad16cecbf83',
'0xd1f9aa42c511fcb4a2d883f99d054bf146d8b7bf00da3852f89ce4a4f63f327b1460d74c58d1cf6fa960b243c2e01ad8',
'0xc442289eacebf3f82db135f9940250f1d8f474b7a275a7d82f7af59e8ccc7a775e2c6f9b78b3b96801756ea1eb17c98c',
'0x66ee31629156d073fbec012746e09b8c6603dd527b85ac9ed6898196e7bc60466328ae16c567d77d9b61e6ba420da05e',
'0xc913a762cd3bcaa7491b230b966a21a4f875331d4ea7bc31e8284b6021c8f105d7af649ab27946bbec70d428b357a382',
'0x4ba3b1b263d7fb602c463e87e833dd8bc8a5bf80d0dd1208ff856fe7f99a8c817749f11fb9cb5571efee2af58e14f68c',
'0xf335cc57d5110e235e204353c24c41a37e98d813703eb3faf3b398be32277f03ecad47c3a3058b9b0176c5b4613416c7',
'0xbf2238d72f6e5bacfb524249019aa071725b27fdfc6f54eb52b48c364fd33d4a1de6b5339318759d9b0d26330518d471',
'0x0085ccee337dd1486012662e11c14dfa8cf38162cc545d32a92f2fb17f7ee129bed388d62bc0eada29ce4d3af479e13c',
'0xf3d77dcde8b81be19b97c7ce51d8dd750ee8e86e5c2548e70869415c1b3bcb0c0f723d9eb679d71503b50d8d9c8f976c',
'0x25c19748a4a99a37e277e5f0bf8a9642b0fc0014ccb6a3e4152bdd92ac9f88024dc21dbb368d58b816eba82bd1827e62',
'0xdce9afb56fe779662a206c7d1503efacaf9d62fe81fa79d1fbfb9fd8ff03f7a11f263be6e4df633094cdfebcbd50112c']
aggregate_pubkey: '0x7455aa9ac6113a04b6976796d95dc2e4d7435ca37f47b9463b10fbaa32759db32ff9f7c4920c67d461ac1e4ebca56bb4'
current_sync_committee_branch: ['0x93a080a9f03a19868f6293f18236b8203f7ade39b78043184ada44b6b363ecfb',
'0xc686bfdfb29a42db21f3f02fbd2b1d025aa85422f3c1fda4c9b6cb01cd49a9a4', '0xfa21687789f01564df3f9880e1cd740ff1b13ee90b04bad9ca713af99f6dac42',
'0x86c9e22ce6bbd2893c96fa8a8bb510c70b4bda4cfe13aed6005ffc79e23292c0', '0x026a024350254b605a8626d4c94fde47bd1f79d18762db3f90b9f2225ea4479a']

View file

@ -1,21 +0,0 @@
attested_header:
beacon: {slot: 14723520837110841715, proposer_index: 708914455403341157, parent_root: '0xbb2709fe2a7a37673269ecd61d58000639c872f8340822e11a3552d91b41a6aa',
state_root: '0x6ce75bc19bb57789a1df782e481c5abe09c4389b65a0ad5b42549317de309102',
body_root: '0x95e145e5890ccf3bf6c1c3708a51d42d7c602312d0ac2b225a1d54e30649e2f6'}
execution: {parent_hash: '0xbe464e153350aa7eb2b96005f8ae1d86ac0a9976f739ab706afc78a5bcfc6080',
fee_recipient: '0xb36f244968b970171cf7172d96a079e02a7da99b', state_root: '0xb0dd1be1bc8246360db0250c50bf5317623b3b85fa59799ae3d41aec0f665ba3',
receipts_root: '0x7b5465a13490ef913e9756f33330719ca6a02ef8a0f920584310eabc90b31b30',
logs_bloom: '0x8466cae634d44f9a0348d452786dac8586435512acf6b6bf14f611f09210d747af5ac5ab842d0b742c2a7cbdbec0281ba2d8d9e5dc70cd263e116227c20eaea6894a4fa097aa6255eecb7e066958556192734c79ad43a07232925d1fd1e3274e5da9c09caf5d6e78ec3d45c7afbc047222a02bd510c4885814c632fe00cd8101b480a346f2d863fe183e8da80304ce648e812c7de3356ebe6c1b32ca8e59f45be4663fff0658f4c363b1aa2b0c98ab67642ec1c74a98a0870d2e6e4501eb4135081c19b45d3e891f3b6f4cd7b63bcb534eb143f7c8ac41f40af480eb54e64e47140faded0f3a195062fef211788d38bb8cb9645c798cb125d73dda03ad768999',
prev_randao: '0xcf51ff3e10783237b556e5c4bb001fb0c561d4d7ede1ef584183b05417d2ce09',
block_number: 13303882776971662118, gas_limit: 2476159519512033094, gas_used: 3861196928343521165,
timestamp: 145476062100691335, extra_data: '0x94b9fbe336ecd65c353433bce1002f627e7e7ebd2bfa4a75a1c105',
base_fee_per_gas: '81703942230201345474526270807396343415355656884426718941656368956572425435965',
block_hash: '0x93e7dd2e2346d520411e9518ba85d96d604efece32a9c44fc86b20d8cc502c5a',
transactions_root: '0xdeb3377801b03df5dd5751b4f9cd0a615b9c5baa1db8d1dca7a52aca52a0b569',
withdrawals_root: '0xdc96edf3b75ae19c608c0d5f821c3e71a577f310bf5da42bf51293394205b282'}
execution_branch: ['0x9e29304aab0d41643d772d076861e0c951c5212f92e2c8b7788ee79c7a03c045',
'0xa3ae98b780a58cdb0eda6e229a231f34d702f3ddd5d2f3e20446693e79f45eeb', '0x8121342f348cfb272a50b251ae31f7907ca06ba7c15a4dbfdc37126dcb8734d1',
'0x768fba15f01ba23ee16af0ef14612e7ecf13218d4adb616a5ec1f2303ec4d8ee']
sync_aggregate: {sync_committee_bits: '0x1f601f8f865427c5ea233bef5cec47d5ea68cc2fd1eb7f40c1bbd573089b1cde688c85ae7756056075dd04a4fd5772858ba89abf024c435b4060b188147e0c49',
sync_committee_signature: '0x60ac5c67c7a2f460b665a3a5f618d3b8e2356a2338279e2d24134795398e286ac0299cd6367986266f073c3a6b400f7d62a586e9687e7aace3dede4172677c6af2515338ceb3591a86bdae014c3cc057ffbcc1afe4127e8b55ad508ebcead13a'}
signature_slot: 6292665015452153680

View file

@ -1,560 +0,0 @@
attested_header:
beacon: {slot: 4648521318796788048, proposer_index: 1612453559776321676, parent_root: '0xf5be6a43975fbfffc33d4536caf80d57263be0e916e85bb73b3abf70c31381ff',
state_root: '0xab646a8c2f5f5517a75e33f99d7e0c3ef26feac9698468cf095acb91b99c6da2',
body_root: '0x49f3c74b8874e1595d74d970886245aff4748a11d499a8e4a9459e76e2c8c42f'}
execution: {parent_hash: '0x5d2842d8744f99171810eda17c5f4fee6e6c17ef006df0696d70912564b7d875',
fee_recipient: '0xe61ca10c2fa63379ff4e65055786f3e4f26ff18e', state_root: '0x57ab107cda466ca72a7da575fe1a2f86711689b2d86df373a98dd10d4b64316c',
receipts_root: '0xaf8dcf5097a03b4c0baaf65a37c4117d7216b350b65a2d734e8573ca962bbddc',
logs_bloom: '0xb068254e1dbe16fced7eba29bc5758d6d9dede4b903cefd130dad934af9643798d5930a96d4f336944b4cda7192b17f76834e6728214f4bc3be20d3e21e2becdbb6d722fef00796c7e3ed9f1b7c30fd6b0d4014928e321db790a0edc4697f9af3a3622c194d623ff3de1eabe37a906fc98f1b053d4fc80f84c013d4ae23054806fab7323ef48a753202f4ce5c1ca59f27c76bf45d48af82c2d6cf478cd4f9da594c3373b73cd7d3903c2f8aae098a468158369dc125e49aac785366cec6cb9251b4ca18a0e8c69fa7cb383c93e912139e473334efb9bf709644d2cefa167ca733e4e673dd3b20e8a4000eadebc911e8928df5dcb92299abf7a28266643230e39',
prev_randao: '0xa4c841a77c846c2a80d416fad457e3b7df14e1211c0f9c98758895bac2b84b12',
block_number: 1698764760070597267, gas_limit: 13290624874865746771, gas_used: 6303106193236851574,
timestamp: 18335893103171502192, extra_data: '0xf7f3a639b10c7a9407dc69703e279cabbdf2bf1537bf',
base_fee_per_gas: '56716435167872726875281896720540038912335992859832623625387483561673156354570',
block_hash: '0xca492bf126049bd030488e3dca248a16c44d2d7038c64b9e90e457dcd69da318',
transactions_root: '0x9aff52ad021d855e49029c8878de0b858b3b559b9cb98ae042e62b39642e3bbb',
withdrawals_root: '0xab14e4976e1af2983f637d57360f03fafb3d0586be3efe254ba32d612ebf21ea'}
execution_branch: ['0x1b42aa11e8fb2cfe30f4ce521021426eb22824b1f2934e85c134c8d71795faf7',
'0xdb41e29dba690ef7359a4bb9130a0d19f8ab17308b07640ebbd65131565e7bc9', '0xb8ff47b1ff2963f7543f3593b2004ae89d30a016961ad78401b07ab87aedb744',
'0x133f19a90933e816b57d53dee80bb438cb60bbbadb6581fccaa3547a0428149e']
next_sync_committee:
pubkeys: ['0x29a24e45031507af4f61d534c734042b67ce0e36a549f93e8b72644a4e3874e142a438d4e30eaa53d235125dd1cd7853',
'0x07d9d4cbb4c814d3b1a1a31b8225f3c6afb4613552ee4fba3895a74a7fcaa9b86721c5d1377ce34dd344138a9642c689',
'0x87b895b239bb21aa86f4b1c5ec0cbeded4cbc5b30800d2aa3f5315bff175402624fdf1ed5986b2215e15e96562b2bc9a',
'0x9dc7d4389238f35f9aef60d805698f41689802ed877f55cd5ebd55626f21408100f55b910c0d33a4d94961c8431892e8',
'0xa824d9aeb047e88cd0a2208b42d5947195633e838b3bf0f5105618cdc41079f47bd45ccd17d4b72a2b10dff0af30c663',
'0xe14cbc2a0e00886793d02ed23032b2e98ec43390192f154c682eefcbd2f25675eb8a28602c993c55bd23d0e065636ea5',
'0xab86632d35a0726e710e4ef614c358866a150ce1fe98613afde11f9de898bab2d4604b632f7cdab23852449d85bf02a5',
'0xa453a3e1189bfbf286f635fc689ea611b1ee35b19c055519075311a0abe16a8dba1fa9e7dd68424df1c6dbc11dc5ffa0',
'0xd8a10631f5332b20c772cbc98a106185b52c8876d88e3b07cacee07ec75e33d1948b651b3c0e7e756769c2babbd92cc8',
'0x3f97dbf5cc1668cba5d489a41a208d0f063fcce7352893bfdd69f5976d07e62a42d0cb598c819535c3ca70ee1d98a1a3',
'0x3d0821b80e0550216723803f767f5487032c71f4c0e9ecadf4f7af3dc3f121b6f3944a39b23dee7929b0d8a6a13446ca',
'0xb618fbf66ea3638dc61a2cf33c42f9d07fd8964afed6bbb0c2f3f2857fbbcb91c1935c914bd274603fad2dc2b00ce3f0',
'0xec95518f6091ebb2bc41fce9c1455f5ee38b9862020e4736d2abee5e75ba6d0a1c994a6f97a42f53feb6eb78d6d7df17',
'0x914d2e52dc1932f29f5781228b8ffcb73330db229194507eb77378e0f178433f659f58fd0638649fefc08102fc99f648',
'0xcf0da3217eafacaa0b6e459787f2fb0f68fc367ad5aea6c24ba5fe654c8eac25b8512631671ad146c65afe0c289463bc',
'0xbcbc723f398e18652a36843980fb8e8ecd064d004834916dde550bfcc059306bc7ad1c806f0a82fe201cfcbc481d9cc6',
'0x96cde366e4b7d5662e2469776b886e1282547f622cbd9d102adf702b7dbf1d786d8243bef0f749b3f9e0e5a0065ac382',
'0x879fc0599844281e55e4d9d22622b49ec405b54305525ca420e694f69070121158ad7d7f02cc22d96c655df33362b465',
'0x461938318876ed41f9c95d8c1fe82985ecd29dd5b9a622b2c6ee8ba0a51c75f4b6dd59f4bdb3d4906c09f84bfa84f4cb',
'0x93d340308efadcba89c6912a4c2f7e1d00cdf5b25d23cd5adf047e8f5b0832e528da1a224a4b0b3a3e96402a05a7a84f',
'0x0f7a353dcab184374f751c6396aff391d82d3605c8bf2fcde2c0c8cbfbdc79f4ea749074afbac2e2b425e18995f25dea',
'0x25419164e044d301f2a07f4b587c85f5c4d3a51f6ff702ff4873aa22065061c370581d68f9d1f80a982e327740aec4e5',
'0xb90a86c2e3337d66669a855d92a43abf22c67d053d6cd8f4ba3a12633e9b297b7017d766567883bbb331f8de8cf0c27f',
'0xe5908126d94b7504eb192c82ac91806cd6d25c0b77b3798753f9de8539bc31f1dd5b02d7aa0a4335f95a2f160c0b9930',
'0x8771e8d7f1f870ee6ac6b28ff2265477a6581ee0b18816f3483227333e5a6fc1b2b0af9b47bfcbb26e082c3fa9a2ac4c',
'0xe1fd5bc9a93c5da1e14a42302cb5158dfe274f5a19d30c58f6d73d02ccc42dace0569ff3b2c91aab57fa6a963afde05d',
'0x6444b90e91cb109ae7296b9d8c77c6ad6f61889b936a6b8857668a5f489e0765bb2086e149a06282c65dab8d9b4bbcd4',
'0x4c314beabf0315f377b8e1bc202c58f2a9a35bba73f4b59658adc4912f850de380f5b62a58cc50b38632a9706b58d394',
'0x818ffd636ae9c752fb8d55898e7b4d2c6b3144009916cac9e6d2edbcbb544739399c77f8eec47b43636c903d25169bfc',
'0xdbd15132d397528855909c6c235934ac5103e75da07991b7ada98fcc6a792fb1237ce4fe8908a017bd74dd4d01ce929d',
'0xee9ac6a2b578cda629367c174acbc8711c1e1baaa5faf34dc3a6826d2f3eb2f962e97129631095cfae907843f3995092',
'0xf72d85b5cb6ba3e9725f866046c91f52289c1663d57c43e97a81fa4e50ee5036acf981b8bdffbfbba8e5fd93351bc379',
'0x79a246512abdd8a49b94cb02601071af02c261960a98277b877020a9371b8cf312252d497b73208e34dfb4c8d9df3d5c',
'0x25c209ffb1ed11cb15ac7f4467e0e8adc5da961197adff92b602a15515c395d5ee82a12883d4b717a1c851e730cc3307',
'0xe643cb9feabf1dca12b3a951e1b6be21d98fc0675af672768e1a19a5bb0f4af3579f6354e0aa5c41d7a7e3586485c72b',
'0xf15103269251c00e494d1aaa85353b5d11ebceab9686e5ea70c006b9a073332d37aa43e9ea37a26707434968755c4af0',
'0xee610e6e70afbfd2d9ee3a63cea54353230d0e7b7c5ae15832e4297a491a6b6c870224b9d71e031237b96e8732890cc8',
'0x229269e691d13d7108d10cb1633bc139766dbf075afa9b184130c87a0e7b50107e985a731723e16edaf0e3cceaa07dc7',
'0xcc2930a71b0d188b1b936b30a7a462a0e45845230eaceb8c7fc6132ca2b66a1d28493900ded37c3becb8852fcdcf59f5',
'0x95624ce026812aa51e1816558db5916d66f7fb640a86b75ad6d508c7ac0d3526dddbfcb87fac2d9ff776fafe309d2a61',
'0xac7f69e40bf30ece8cf33a76bb57a7aa48317c8de3e22b6fcfb718e49a34ffab0dafd48f727de8216f1f5126a5192fe9',
'0xeda043be04256c5fc0f86d9a5dae994b990c17a0da0ac1f5eeeec0605c61ae3aca8cef65e84b84dd0e465e69ba6a2e6b',
'0x40b36b4052e277d7acf79512cc8f7f256c821baecebda427776101f557720ddbf6159710ef5c3fabfa9f854de45c9e94',
'0xec1d4c667de748fb3c55a76d17bd44bff2a2f94e67ced72f247451b6875b8195d26110e70e1aed48724bb6318a6b585d',
'0x1af861d59f2f738c0f70d58cf4824c33355f3cf45e4b7b7228ae35b6e9454f446ad9091f19164b7254caf935f4cfd1d8',
'0xd844026b30acda4a41873031b8ca3917c2725ebb529c66e4889d251447dd87dfdf6c4e48f8897954f391a87a88d42671',
'0x3362535af5f6b783452ef3de4296e0f098646abff9a8f649fe6616188d3e9638fe9ae02cf646224b59cea670c66761b5',
'0x872efac5f3717f20a157d0c95df439d1251044edd6e36efd6fbe634701617f95a6899e919f33e560f02f7472251eba7c',
'0xa07c7ac1299d6c09c361aee106ab91d9edfb312a13e5eb9a08c04fd0ca6773b4d9df567c77de0ee3736085535c6f47fd',
'0x188ca16772852c8b45d3b2a04b7eb6122dc57183205b9ed4583638b4d85632f27e131f6348f2b2c1afd260f89ba42e41',
'0x8f98c66eec34f9281c976b56eda903b07455d4fcc032d595be8825ad0d42914561a112bd8654cb5345539b17c5bc6c22',
'0xe2bc9c626a8fa00717b928815d28bc84743f5609403033f804a8e89e03d7e2c10eb0254b85d5e2635d60277e79c4ef0e',
'0xf35058c9aacbd898258fe3538fab95fe5a11b6f0bbcc22449f2565bd50f70cc61c2b4bc47c8483ee6407d486453e269b',
'0x9ec621d18d636ae6e4dbf5e04d1c08a541998e255590e705c63370fd4ef2357a90f2fc5691ab9af2fb593ab70daa2470',
'0x56c3d373a4d1b70397bbaf41eef0082fc0db96ebbdfb546c90283aa2cb00a5de2b8c494aface4ec45a1fd0867f6c0ccb',
'0x2028012e2f785c04b5f5472b286a2c5100e9b8f4fd264d4fbc9a3e174b4c6c95658f33f44e1d3a61504144fb0194c0e9',
'0xcd166779eeb3535167afb41bfa128d58c342481efb13b41475ba2cae4faf033b666d453ec3f04abafa588df13c5af805',
'0x1a27b4f2bf4f69ff73a46a411f08d7089dc2926728b2ddf26e7e0b665b3d0b72555a4321139ab2876ffa44ed370c9ec3',
'0xfb52d8abbcffc931a4fc739f7596340dae887b7bb4ca8e920e4496880d5be52187c8585145f12882deb8552034ad04e3',
'0xedc5da7a4cc925adb3f14273f0ad17707f9ad913ebffe1356e477096ab2ce82b25cfbd4d52050cd831bb2bd70929c952',
'0x55a85bc65f2e1354606fae3f62ae49926bd011445022f9f4a9cede0fbefe895fe7e03bc065ea7f0f86e50ca8d0825833',
'0xa0c6f0f65f399bbc5a759ce8a4fdc3e0b87cc7624f8f07643c597b92e7ff31b053ce2a16225505a3a160bf73fd41598b',
'0xdb4478ffa36662f9a4e9eee683a7cbdb226d984ebfc623167dd21ee7a40a0599bc8fa098e4970fb5985ba60d42e110a8',
'0xd95deff00f04defc028f4bde4b11dd9c57b65293a6b3ce7d7b978cfd565e021caaf62872d8fb3790ae55726d9c6e8ab5',
'0x9e9e17966ca69c3b80891e1e68f4aeb9b42e78a519e4057091e993f717f51d0b01291af3463fc89fceec920200dd6b68',
'0xc5340d62bef3734ee01e1d2e2cbfc51919cbf2d12bf1b6ab542d42a09a34b4a5006671823a731c9abd20769ce51fe688',
'0x7b0aa7f2665e4042677f1a1ca88054892c433db8786c27053d6440334dea672e4ec689142b24b1090c2e225276a2e91e',
'0x671fb74f3b877157fcb5c021e6f0db0b637ccdbf166990d75090803a792688f861e414ed0d4997c609c06fe766bc1464',
'0xc7b320b5a9a64aa4158b870890110d95ef61ee57a13fbf39cf331a6da297024437e56ac86ab6b496f3ae22e5c9780dbf',
'0x23fab13a907e495d341908af84cfa4e7ce1232cb35f45abd285d688fc4d183b526214025d1ed1bbf0a23292c556b0e56',
'0xc083ebfeea7f2a845625b88d2fe8650b5a2379072c793378339b27b3b8e7216ec36852197b4c87fe06946bea6be8c6b4',
'0xd75b8106c6e218ee3a69e41502668323e86f52e45aea1af2b25d9967954f4c85c124a661bbfd3ddc022141f01c121ccc',
'0x9c366c3204417b6fb7cfdce088c9595774a879ee495bc0aedcd90e3119da3be7ffe728661c11a05d6b1aabf6f32db6d8',
'0x4dd2f5e7b166a2c37ea2627b08be4b7bcbce1d6515d9179eab09bd985a4b89ca02a5f272a77749d313704510879950f7',
'0xf96fb78f7df339ed9127bf73248d41fc6c3dca87bbfda8f6f41668c4036ba53e6fc1ba06343e98102542a31b7dae08b3',
'0x54d3e6780c0373fc9a463154421b1abe5d620be158ef77a449f6076bc7b443334c96f1ec79a5f1eda5d4790664293747',
'0xf3be7018403affcc13b28931d572a80fe1442e86c0c03ac74b0ee9a8fdef3ee2f66b28e1a1ac1e5e20d3d9fbb36553ec',
'0xc36d217dbc4cc8b98bcb3668a14cc50298808fbad808cf7461cd4a252931445be1cbb7067d57f265df758eddb8fa5d27',
'0xbdcd0e3dd319d98da1256bb6a36aa75fdceaf06f33b4de396442c3b3060bdbd012ef61127a0007c965efb3ef0118cbfa',
'0xc4142d817b895e94ebbb6318f22dc43319a2871fbe09ff37077b9e813fb745de296d7645f5841dfffdc2fb00df91b7b3',
'0x4be0441cd434a2e6fbf17cc164aa257e334da40ca8a902612dfe4acf3510a8594e09caad8e2a51b2788ed44d755ee26a',
'0x98ef2a1a5418fa362ba827cf9600eb492f42223e12023d79b8ea12a9170327c4aa80c59a1b186b9c187e0cfcb6e1b4aa',
'0x976bacae77830fb71f136f60426791db2339e9facfb506f9318faea2e7dadf84ac9188c2242e4e22cb5ed3cd81ec13d1',
'0x2fb68033da9b329bb6cf220a451d7c16ed80101367cb8570103d1365fb7fe4b772e7e1517f2ff1820044c914a4ea976a',
'0x2cdba08a0a6d716ae496778d12028524b3e783a726de5c5069773caabe3aadf652049a34710bf76cc923a888a962868f',
'0x6bbe41084aeb5ce773102c1ce03d98306b60fa984ddd750fe1d684d15b83f0f4a7a91bee1d911f69d04af5ad4223ceab',
'0xe227850a2991ada1f9d3e88f113a81ed1497875ce259ed97231f93570aef968e90fe951833ebad1696f2f943c1bdbf5f',
'0x650cc050957448a598e7f612c39ec7b9c73c68acaff7ccd9f8e45554ff2223f211ee5c70e93b30423ff1a78638dd4a73',
'0xed05a1eb585c9dcacd5b2c5e9276e703d744e58158e24e33b2aa27f8863d6b092e03f094a18400a8cfb1352585b17678',
'0xd6b5ab29a574674cb70f2e56cc97d9bc89323816798d4f1fdd94b0bc1dae87cf609ee0b976f9d3c473bbf69bcb0da6c0',
'0xec2996149b095ad5adbc71f6379dbca24be9f5ac909787fc34227e22bf3518da20c84ea78d0411353fdd278e18af21b5',
'0x28f95ce3c02090c34642dca472ddd075cba793d685847b75569caba195aff6d8213e49a965bb1f4c2c7389bca8886ee0',
'0x780de8a78511fd67223026d58bcd84132c375eda066fdab540744daabda16b44ef716cce1c4b44f66b4dbb33f973b54a',
'0xc00367d63f5d9a6ce9d078aca29544758642751ed1992fe6503d9828b18e96bd2d445d4ee0b5565f3cffa3159ae22935',
'0x5102277d0407e05801a8bc16401d242476b47bbba3e720f781f6365038a0345e49d9124767f96f294ed87b7875607a53',
'0x98ad6ce61d03a0ffdcefe399f0c2c7deaadeacc305bcfffe8548bb3517813c1a941e6f6556954a26bcdc0b711e249207',
'0xd7354c2511e44c06ebf93d737c1e173d66b9ed8edf1baf87369c42502665545fd4a8901739891477923b5621edd702c5',
'0xac339371d8f996fedb0f5bf546e5df56af1660e0934c46c278c209cc4d2f0cb67f2e5fd6fb6712f590ae222a6aabcbe7',
'0xb884c62e1f44b7738c271c96839ed3a1010b689fcda6f99ea2901195d55de10532ba056bef65a376ce26863b0dbb3765',
'0xc187815e26103dcd75ef6e650cd8cdd144a2e0de28664cb465dc55789993fc44ffdedcda0451c1796444473f555633b0',
'0xc006837cd6fef5e1bf1c97297857e2a703bfceff7316f4d2227b5bab9dcb9efbe803b12786d61ccb1cbdfba496212dba',
'0x38c0e2c99ab2fccbb7297d9f25ce3b94cf84724718b6410e805cd299fb1b1de564f33e61f61a61b4a044ce663cd2df0f',
'0xde50ca80ded4a1df429b6f84fd0c9f9e78f1f9ea4d43893dcbcadf8c45606ab4f9bb19b2aababe8e3bf2cc127727196e',
'0x9cf8f0f53bffa0c7959a66781d24288cbd9fc82d8ef8213de34155ff50f26661217bc09f07fa1f0e781f33a8f595e597',
'0xb76eb69154d20e99157cc3432f02eb715bd91c3529b9fbfb16405ee303f570b3255bdddf46c9f61735391a5811959b8b',
'0x98026674c63113501ac467224bbb645e1ea87001a9af0f03e851263d70ef43fb3835df453a2f0e9e12b3b7f90e3c88fb',
'0xe5487c8cc332a5c41b004d2378242eae692e1acf81bbed621a6fb1a5f095ba774640648e1cf1a04d299ef8abb643b799',
'0xcb6b4bd9a088d8f3e0b3bc979121eb5ae39d9983b7148399c66e3921140de01b65323bbfbfc03bcde8ef6b89ce17221d',
'0xf01fad627e73e5242d7735bd1ad139548e879fc41676798888562acd9b35b9bbe09765756a3f9b72890a9597d4e9fe92',
'0x9ee1e52435cd7541248401fc992b9c894f65180c0b4d5123160f210de05de8a2dd06bfdb47d2e58c47681f3ddb5b75ce',
'0xf0b856f3636e29957971fca6b692d5a2a2bf87d43a9647e261e3081728d28fc446e7a6fd24f6742ed6cdea9d80ca21c7',
'0x9e65eccc7d9b8fdabec8e7d9f9b547bede3e0e092a3000f1e818577478180f9acf6d0b88102043b9384b9d05559c62c0',
'0xe60abb566d980af2744e88efcbc5dd3303f596985d532a724efcc578996caa1d933cb69daf528f2fa92683b11c55f2a9',
'0x3ea6121c6a67e81a142a8c729f2aeb39a383b324cee33fd2ee11cd00a3865772b3c98ca9f6d79030e80328121355d1a6',
'0x4d80c880b6faf22498c014b6a5cb95bb583959248759e9c14a7a8a30071fb7a25cfe690bc7581611491548ff9b4a363c',
'0xfcf42750e024ec7fcea704df172ad2e28abcb64d8fa6197cf2e9db37cfb79a6ad8c0ada8100902752ae218957eba49f0',
'0x64f94521eb4382fb1bf8709853cd6a1eac2ba62e2678ef8f871b1b916dd9410f36a6499b1604da2a51925f583f55012a',
'0xe6520e671848d9de2a3016d4abca7ceaa9d702f846f9168309c5ff9fc22785fa2ef5a99bfe3f4dd2c0f24df7ba5dd2d5',
'0x730ae2508532473b609895bd67cfc62b2405e2bdca439ed39721b33f826fb46f697e7f194bd285db4778691d55681a3e',
'0xbfb8f16fec592a2331f2ee97a11068065c6d2e79db3ccf17ec54ff3efb430f01cdad74555df74b69eed79c22fb6bb6d9',
'0x05727d897e9744a97298e98a0765cecca18dbfcf160199f6fe202ee1f917822f00585219d42c36ddebe6eb0294e2606b',
'0x7e2e6fe08038ee96b6504746c31d258e8a9d8f6a0a5c9e1ba8da4f924e6f79016090bb4f14f77f684391d65b31e2166e',
'0x65de5970d981501c00e83dd8427887b678247a5e54b61b68172c9dee499071bad16beb0597fef00a66620f7bf974f8e5',
'0x9e4084e6066df24e9eda791f447a1b19eef9497b19a12b11b1225a9f7423cc11171ac0f4fcb01d85bfc73db8f002be06',
'0xa1581db18d9a073ec54c0ed79462f1cec66a4f00ab93973bfe9a37085e280fdf9cb16b1b2c1721b93bb317580139e2d4',
'0x0630219e846f893bda32f7be62c2b6e380d1b0277414a8ad96b426a39456de2f4bea1e894a0eea2505b2a492c0931d97',
'0x6c61608977c5624cbf4aec4d689372ac5bb3f8fe2c6d5e52eed99216e30d53b41704005626877b9dd87fd3f1cc19e81d',
'0xdbdafc8a60748ba58d4bdd1f94a11a2f29a06f3c22eb8dfe4d50ca3b1ad708ff8ed87b8946dce72dc035d5e4ead0867e',
'0x5c4b0ac35521ee5e52ac77bae45c28e629e3d1055e5099a3ab802ea3f5803db6ff870deca425c0ac2d4d15d0c346d2f6',
'0x14438a0f0842f009b6dd170cf4a2e4a3d0d8500ea46fb5fdf76551a9c1861d4a4978a05c5422f0bece33862a62bd80e3',
'0xd8629b12bc89ac2b5297555a94eefdd0ba5e3f05bc1d41e575335051aca58506db426fe73a7d73e6e179ce4ce615b8cd',
'0x8f603b5dc0e5c8bd1087273a4aa97949b6d631cdf590bf1b7e023b3a399e8d15faf432809f29167c384b1d22052c3730',
'0x45257861abfd69e9bb271999df681cf7b5b974c6c4fb286b5dd37a598cf93f594fefb0d9a6b7769e28154dd4800f5537',
'0x1f9701e8bd572e12dc33a8db949b9d5740082bf96543cee616560375fd91170957a7f2541c560b79371b0fb9aa7fc61c',
'0x5736100704270bca5f2133760c3b3f98fb476c5a245f2c87a8b11fa6d5a7c0e4387009826ba04e0b0528c15127f8b2a8',
'0xccced49924a9036c022e5d098ef08a1739ecbed16b552eb96007f574c1ecb6b2e080a19193c269efc5e35b5d9eb3bda9',
'0x4556a7286c958123d25e472d38156ad573e07c220c59f640cdee4ec2503c1a148d4be61515c8237ea70f39809055bfe2',
'0x565d8cbd38fc930db2e8ea5b8399599388b73aba86b0d1f486909cbc72e8be49a9dd22a13b0bdef488fdc2cdf0056873',
'0xc7c34acc7399fe04016af6a21608e251a484509880a1c64d3921e354f1a88982eb474fcd70c57d35dd95c213cde0f9cc',
'0xba6975ca60265d842ce5a72838c98b2ac002bcdab659e5c9a28cb90620293c484f588abc816c9f78ae35f2cc88543974',
'0x71ea588f03ca0a6919f93c7cbc7ec25647ca155e397d8c2ed3bed240f9b3e3fa9c51bffa15db274aea21b0fedd455267',
'0x44f7d07d578e695e6b7eb675b62331dd1b99402092b505af228b5aa5bf8efb9922c1ae10956c53d0f28e6c68f09c81c0',
'0x42b808ea83e56d318d65a037fa078b02c6219ce4c70f9bef56ffde4f5a66cbdc06d97d99e866de3409ec6c0d15b7357c',
'0xfa64126e2f52723cd2f49de1e2e67bcdbbcc6c34f4f5bbd5a918986c5a93f384ea68792365fffabec27588d9c76aea95',
'0x64075afa28e6dc6a11e95b5463880b1c971869bee4e7c97b54e380c94a9da13653b4f6cb8057c6d4c0dd1db7f51213a0',
'0x92d8c8f9480484eab59752c12f4a2847f543e34a98197ecb2f5cc5c334c5e3a37cca8d4bb66e3252b9dfa443327b5546',
'0x05ddf5cdbe3e2d67c11ae87ce96b692474f69c1f8b319f45bc9f3768eac20627c2134f2bd3ec96a878ec6584705e1f29',
'0xd43a3f86e986b981f68bcfb210c1821ae3dbf0596fd49686a351d3d5879c8f1304837aa7ac92ef78bf0474cac2e3cb4c',
'0xf2697ee5da9b315d91066371d8f8d20647cc745453fc46c3bf770eb53e2b7355b02a136be83ee1b19d5ddca88f5be372',
'0xe1c3cbf310def9cc204a6be0bd85a782597e94b30c7a8ef7dba601bce92bc5d38a45cc58d2175b036869568015e2cf20',
'0x3f9b3d7cd98872f8b4c4c0fd0e4a20865f09ff211ee55cc873a6ccae979c470ab59bc68925f73f425f1eab31844da939',
'0xa40d583a4c3dc9df42c2c597413ab6ed27bd378e84877fb0a210fcdc690284fb947cbbdf01b49f74c74aac7623c5d701',
'0xff68e0a1fce4d70a80e733e372e4e8d7ff41a399d6750168ff1fb6d4ab7383ffefb82eed901b87e7cd545abdc84b6843',
'0x889fdc1cd9c4089ab2800a6a5edb5c290823bbb95f2c05abc45a716b8dd43670f38880f2128fa41f3b42ec3ab1933e0b',
'0x943235e8410b83d5fccc2912163b0f535ccfd443eecaf8057895e8ba1532dec6bfee382c8d2c78bf462c1142d0752472',
'0x99195fdc42841e453e3119d987c9da5f4df33297c2fbe63f335680df192c264f376fb7b21ef4519bf6f850357075e0ed',
'0xc0f8c59acc16ffb6022142a1e0537e51da09b787d23ffbd56854718be5eb95f35f4e518ab3f4b9f2df1ff7ddd4a7dfd8',
'0x6a7ae4992add87974eaae21b0f068a07c8083d5e0acc2be5d7ba705cb83adcd7b5b0ab89eb6ea5895da3e5fc42373e39',
'0x114d0563f6e5ccb9bc558c57dc60a838f5ecadc2a9a48aaf264f3787ca4bc1c0661c388572c277fb2a64595d8f785825',
'0x7e6bb6d26a139894b48781de91efe73dab813409ca91c7a89a32866a6f5fef5530ba6e5622d7d7ce585ddc01bce1534f',
'0xf26609b87a1a38da8d53ad50cfc5ebb29d16555571268a35869d377e2f3b5a3f2285256f4a3187771fbb821cd7a79e50',
'0xdf6dda91bcd8bf2f2843c6dd1d70523d26ffdb450ca4f98bc35665290d5f2afa2eab8d8b7083c079b9f17632a2e5ac64',
'0xcfa6749c07e4818f667cf78b4d63b721246bb64cda9653a1f89ad777a4a204fd6d3d8e51d57dedba710a99e88d4e4d10',
'0xb0eb407825aa6851b87499be34e99c7fa32ab10177ba2485a2eae24d75d163d97ae8d51d5c3cf8608aa600864d9f8f67',
'0x41a2b893e313a12696f37b1f10424da97537ab491a114376778cfe2ea55e692189d47370b6f74e38396d2219ee9f945f',
'0x08044e4c8727ad26e5f60d979de85d94814f9c7b5e6687dd869b455cd56fc6e3e81bc9f96d82e7d10e60a09dacc0a080',
'0xccb21532af7f413306b106d6117f396d5db707b9d93249ed727c0fd67c10124eb68166658995031e531a55b5a3bce790',
'0x0bd0a76c6294b5740b65d2091dde1746e1e22c2a0104bd61b7ca4d5718bb706d5f2e64d594fed80bdb9c12c3afce9aac',
'0x25073e313242299bff38905b4bb3eff24acbdbe20204d054261e4cdd9820e4f0e604df91fcaaf5ff01d4daa084f5f60b',
'0x4b13e12dd360f0af2b3fa18d112e43e3a5b92ae4eaa62b4ae2a048d3478619770c1beb62f5972784b526d34e33aba0dd',
'0x7141b18724e2e465c774c9e886977030f1f973a3786ffa4049dc89c85fe1526d90fce3ed53e0bdfe4aa349cfb4527de9',
'0xdf854d3eb347e3f0ae41fc910053e2edc6d47e9340adad8aa4072eba507be742909e9dc47f898a8627c2c3b40473ccb9',
'0x1a9adb12beb925a66570af00afe1fc35ddf86fc3a722e6f28cda228a51b93c00deb7adc4ea4ce8969cf791957e3912bb',
'0x4c589dbb5c12d8381469a54e53198f2d0d4408e769ec6e702d423b93d73348ad484a77a001f738ae5ac34f525e1077d9',
'0x843e5bd71d93d12af7106eef1d3f9b5fac6788f4b7bd057d4cc0e0dcde44b509c22e46a7de0c69420d7664eb186d1444',
'0xcc8eef6637a97a1564ebacbbc0fd49dce65c1b3b0f611afdd9bc4a52d8b6ddf27356c9f124d19bb0e1fdfc7119ff78e1',
'0x3a032f7bf5187f18ccb8bfc1fb3ab84440543cb20119e7037acd28408f372b160268d382a0079864dcb4cc0354738d51',
'0xeb20180366ea5d0401fad0b0adffbb1155bac090e7769878eb57d9a40a65a002b40e9cf1659c8f722c0ef33104e4618b',
'0xd7253e059a595c2d254c3651488348cab483639f1cb083612ec48c9621026062c1b42aacc6b3b2e388732f1bf03180ca',
'0xb55fd0443159ee0ac7bf2a0b88eda66215bd0efeded2deb5c8ee9c806d80c09aee125bec912423c28c3ee48e491452a7',
'0x91ad621fbe189d3fab05815f6be514700d3f23beff2fac696cac3343c192deffa7de33436336b52e25d6d1b3c9b02457',
'0x0818624afd64428a9bf4f86ee302c765999501aba45649f6f76fe41ecd59d0da8528d8a6b4b02c8fe3ae811f12251632',
'0x36b46e5bce128e668bfb57f49a6a371db4f305ddc751f66ecd21d2e46c52fe3c81e5fbc9fe530d8741166f658e42547b',
'0x8e189901824e21f646a272d9a67ab4830f133a44cda7af3715b80df50d779ad2506fe489e741cac9978ac6913fad4aee',
'0x5887d695f66a319e6f4caff5ba7e906417ca8d05e539e999893f771d64da0b15b05b161b5b068e1790518341f9cae75f',
'0x6bf033c530f6b1bdb6ad7c0948a8ad6416d7782e126cf7097cd205a52032b341db60abf5b64a573b418a6ec47bde2f49',
'0xa6cf66294dda60f5731efda6ac24fa2bc5ab4321db1fa21452bfd262611b7490a7323e4c301569f309fd45457c3ded4c',
'0xd3e008405d8c247c8728e88a6fb36721210b40ca824b213d4c6a4193a313bf36cafa1e34a8e945d65ac4b8e234441553',
'0x26feeff2f854847fb885a6e1c254bce80f9f117d327d13393216a4698d218c20a2871f5447bebcc0eebed91052bb6491',
'0x513a7df1f2389bd223ee0dd7429c5b5d1f46c8df6d5ddd10107c4435e1876292cf61193cb5aa8ce55a123b2d95a4775e',
'0xb2a968612cd8f9bcc309e4725c77d822f0d21d4f878f33befd17b9f4b353b198900294a9ad49590c379ca288229114ba',
'0xe92f771fd648fb79baaabf33aa091de69b31bf2a364d4a7d82915627aca571b9aabf891c8266828bf5d8f23f57557d96',
'0x30010526468f84da5ec49daba3ddfe279332698c7d12e2c481a48dfd2039f550c088623fc8a7fa41cffea500f64767fb',
'0x03b1394d7bcc4eaf9fc7676b98e285ae2b5dd764115f95ce76ea14353a00dae840f2ee46b33f8f08166460246816ef79',
'0xe28fbf8a63d0d9ce42a0e47ecb75a7e0f76bd659ac5cfc28b1201ac2036e8ad2c188b7893a0ee177064cc51b302b39d5',
'0x4edc74fb6641b3c7b920219eda67dc43aed7b6581f1e3f25f922bc1abbdfdb15ae68c70984bbad78fea686db42ae8a2e',
'0x28820aa41044b033db5356ae36130b3c1557d22ae72d5f70d8e4505c21b414edf7b2fe57aaf94e46d1dd695b86510bae',
'0x039732e49d85de7c806cc96c39eb0b22821b35377c3c77fd1f0acdf8123b573b0d433f78ed81df551f5b52b3fbcf654e',
'0xcb8ea70dd804d979e4934677a5136bd0648b0145e9aad37144c30e32ffedc2089ffae133dd31d9a3f83341c231a1bf65',
'0x901e060bb123da2315aec382b9f6739e254423c555580e0fec5a10c97b5f2af548dca7c74a196e1f7b0b676ecaac13a6',
'0x49c1878d171d1f312764cc89dde24585d34358897baa861fe728fd1ebad1c7bad62717c569b905b1b739b8a6dc41e5bd',
'0xedc6d9f2fc1b072f24680b1a8ef2ca8f42403b5a9cfe7d5e7cab7d53a3f8dcdb200afde25e67719fe51d1d3129251edb',
'0x2aae8f4008d3e63dc21cf73a3c857addc8d89f16aebefc81453b8577a3d232d27ced5dbfdca2112ebea5937d9dbb537b',
'0x212e114f54d1aebf498d53a47aa1732e58a2647eab729b3fc1ae5bfc84d03335033c1567e25f28315f167599e4350210',
'0xe1ce327fff1d79d7ef2f597d85d0dbbab6bab0be494532a2e569c90612cf8aa48c0300d1c9aa67f254e54d34e050f341',
'0x0738d47cade0d32f22fd346b0b246c12416a998d4f0abc294b231a39ca06bf914edf5775c9b1ef888a68a3f133a54de3',
'0x5f666fe9f4edf18488a3168edc8cc5a115a702f659220a84a25e8fa40f7471bca75e2005fc0867ac58d54c71e177321c',
'0x0722f48837fa083f867832d56ee8b02e09c49885515cd715ed3e37eba939a2bcce3f508d4f1abd047002cf3bf19c2c52',
'0x86cafb4c8cae1d366c640511f9b74257a319c2f0b8ad16b171736e0367a8ce1ec5857cd5dd581ee9e8408498449f15e7',
'0x72b29698f75f4e209752a23f10d5c17d4b8ef28c9503683c5a4924c97d4287fdc24a7549655367d323769f48bfa26626',
'0x0973819cb3f45bf43bf5a76f99132535760ac8feb4e2d2adc5584aefff4bca1b54929d3d7e647577ca5c634f8b95eab3',
'0x2151918fb791fff1e2ae652f069ae99bf39ceaf4e72b7dd3380fed8287dc634fd2f65ad6fa2d14af3e7f9c362973a4d9',
'0xa586b5c188c7c7f6e864f810443483a464a1fe187ecb7942c69b7df98e430627b6c791f7d99c8769a16b72f75d6da111',
'0xbbd947c9cf02918efeda631cc4cb600dc6d36cb1b03219704aeacb57e03ac5b161985a46edeff919db60e883b079b725',
'0xf3253f153ba1ff98b70bb10e22a209b6980363adeb777c4e7bece9cd53fae119a5f4b2ff7afcafdb0cd526c658d6c85f',
'0x26fbc97cce3435c16493677ca1a1e76309db757f26b3b5c597269fe00b61bc7aea16fc8d85e39288a4493344fd43441e',
'0xf2f15fff1c1f5460d2ae903067100884b78d8398e21b18ae785021c53a9e2fd5976a0cc5d74b15f81a2b1a8c49d4698f',
'0x61710267ff61c8b1775b3c5805b79dab2d83367e5c4175deb390b05691c8972c58f775a6f623723b7f6f2a73b9e61e5e',
'0xb054e4bec46e6a04e116c0e6b82de5b75f529b00b0bcffb7f6d123400b28683dde4257bee24c76a06cf7140c9107689a',
'0x976f6c57788deba203be0cf205f96bd1b017d3366896d84f784ec0c1ac0d997f5a924c99ac5e6a01536f48d2372a4a08',
'0xc0803908f071433df45faa268ce0c40cc76bb10654aec3d1b853f3f74e70f3373d3cdadb5c6e7f4816aec4201a57a857',
'0x0e3cdd0dde6658529bd4bb10f674c26b2fca7c72bd56ab23819c8f4b2dbeca5d3eb9ccd885bda363048349d170811da6',
'0xc4b149b612c0060bb135a67764704974b5c3b0d9235e05da354c3036eb372c02acc8049a752f7aea2b57fbbb11e81466',
'0x54cb67f9d7392c65350688f1a1d35d4ef4d9a60731f49baaf7d3fc0d8b34a190069a1b621e0060b179fce94f7badeed4',
'0xb6273aa14d7fc8bb02c1d44d2bc00b67217ce09138f5a1bd146be6a7df7875ec671260dab5bf9ab23410bb7796e15789',
'0x20d53c6f4bb049408cb7d4aacd757ce7999e9c03bf7beb2f26b2904cb5401de4467f6030f37e2ccd636f0d306b080510',
'0xb6639826dbe23f9e714361d1e71dc72e77869a48245d9b5af649aa412a44535895f21c7c907f87c017876a20b84bd7a6',
'0x2a9571a696827a805ad6adba8f000ce969916668e0de7728ef84e48ca33a1d7f7bdb16aeb20c85dc6ee1bd2b5484b050',
'0x80e8e1780896fe282a5e6855862b80d444f1a8d24726ad2d813107deaf3f10674173a33a721ae27de989d880b1280c0f',
'0x16d5253b2bb94aeee43cd3ee91d1a4c33fd5048629f15bb035ca8183d292f6def97d4594d618679aea6772d11f5bcb88',
'0x40ce0bbccfdfb8dec190f940fc5a81de83659be9257b1ff447ae2e5ac923a0567316ee018c65d0f7959dfaa77461790d',
'0x60d1084fd7fdc3b2ae9a38bd82561aeb2f22ae8890578134c585ff92aa4b5d2fbc7fd32698bc0600a449ea57a28f811e',
'0xb05733de126baedbc5a526d84aeeb97842d9ecf6d7c2423db0b44e3475b26b0547941e8ddbbe8ba7710e0a73f3c38f7e',
'0xd95daa132ff886f40cee4721565448318e14bb8c3f7805aaedbaba54eb6690aae5abc5b56ef6cee0a2d1f8af8becae8c',
'0x8efde43e7a63491e6bab1cd2b3f9ab33fa0da8ed941650950685a57803f6a103bfc7a4cac2e2ea017146f54c6d904ba5',
'0x6de8151981f6df5ccb13462fa656fe88f3d7033d32e105e456211d66978a439581ab3a705ee56e5fa8115b16a4280519',
'0x7c59f3c48aeceb1253c6767ad19b4e5ac36cc857f72a168de4bb2943fcce3bed55ff5db2d2ab8acf99a3e55849b11b6f',
'0x814a51b25cc8a86634b12612f15970ad327416dcecb33b7397225b5d04848912eaf25c282a7dfb7b3189d240ba7e5739',
'0xf79a57b7c0b8e2a6e537397607b3aeb6cf0e0f9bf64a09051191f3b3812ad8cc0fa13daff0511cc09eab7bc9d97cffaf',
'0xa24e769cba88308ca75771c9f3d636119f4686778ca863e7cc4313c1acfcc09b29581718c5ec075c2fa6fb188c620ee5',
'0xa7a248dd512cbdc9539a9608de2998b41faace789a1d77b61bc926c18068e1a181097269d62e95d9433ad7a728d17623',
'0xa1fe48accf143debe0b2f6612b066efcaf464ad58b70c42c87e40fe0aa824eb09fe4b16e186e4e47e7605f9c5e14914f',
'0x54d46b8b60ca3cf9b87d81697e44a446243882bcf1662027936c548d6eb4dcd99848d4c868a2b51eedf4bed25d1acda9',
'0x416672eb5f597b19b72739fedfd5c1c7005dc5a111e6d588c7be52e8128c0b1b4c54244be2c09f2c8ea8a44ec24d11fe',
'0x0e81f2b70770cba5ec3e35c8d5f273ee6d0ad37fe1c7072686a68c1d304074238aa03d81d05ecb99e313633fd1a23619',
'0xfbd96b93c4f1f727d4a9a91c8676de9c6881b6f46c64523a2493513ae0eeccd959765f6316f08343e5cf504d1e21b9b8',
'0xb69273818c454346eca80099c9090b13fb75df01940263b6179038b12dfa2a2270d6b854bccf0362c038ef1cbd0e8dc1',
'0x763fb358f9eb2dcf4c1212ade7b43478cf35c755ed147fe18511e0d74ba90e7ebef2676121b9912d0842450bda37e899',
'0x71216e58b06a64e78b0a264e66f62e27362e9a9ecd77e4537307ba5fed86e58c428cd2ef636d2ae2f5187b60cbf2103d',
'0x3d62337ba49e19d407cf856c8018d0d68d54945a30d7150311a820fd6ebe2c4562cc565f7296d11bc305ff11deaa83c0',
'0x1d7748c2f93b6abc24ae3230890d1e695f0bb0dd5bc9040d7d2566bccc870371142a91c9a804e21b790b4255a5c22308',
'0x8fac16f2807532493aefdfec105277867fba79cec853c108bff4ad3bfaa1f4c9284a1a87cb3299f208feb58d6fe0d224',
'0xf05cb9a988ab56219bcbb8b0bdb541de726026480e0c9bcdc9554ab63d1d1046d8481ee5fcd82a3a8c96bf210f08fe66',
'0x8d30b71ce9d64bc667c40cedda32b40235e692b0b3f30ce11e2667a435ffceb14c37070cc49011c92d64886a2c5b59b3',
'0x34b1076b00281ae2c3b9ec44586cdfdb6207b40ca94d27101d9613ff013ffcc8ae526a79dd61fcae520d4e18ad08a156',
'0x85706f7820bd02263a42f3843984f883d1f715a890afc19c602f7242ac4f774984ab6b20a6ca5d3b55987c8c5529010a',
'0xf8df3f7a0fa9f5a90ad1d9f3a46007c41f8c4183a2c23ed81f42e95e523eac14ce6384b89fdd18624aec72f7cc7976fb',
'0xb7a67bcc0e05f92efad6100a2e185a77984a7dc038b9bc8d93f523e4232a156358ed291dea7c4ee08336894e4996f0e1',
'0xb2386529f5ad5666b31048e27043d98fcae153b5d601e2b6e01d312e34c4c8b4065c3572fbc6c18b6cf7ebdf64fdfcfc',
'0x9e47331ae785fe8b44276d029107b370895f5fd6191f84d38e31f2439178f833903506bdbbd22b8dcebe6caa042b4b34',
'0x24974263eccc78c0be92a7d18a47a73cdba7c79a6c38e39d567537fba81a322d9e26f8523123eb6a79ceb7cedc0eced4',
'0x59f6523eafb454ceb126055c53d2948c0537c6e41a3746083131b165b332045ebdf682c55df5658c1582245a4a89f383',
'0xeba21845f57845eadc9c6755f9266e9d882a97634c12272a0350dc57c73ea9187b0f8e4c1a76a211a5bc901f7dc95f8d',
'0x7056beb077924a105d00c071873a2c6234934176a8b9f11a25997ef702ae176f0de2aaff5a8da8cfefd224d620c2fd88',
'0x230376980362887c7fdf4559e68c9a7119a4fe8a3db656344afa0caa1a941e14414437e756a39fad23ed1fd5b160a29d',
'0x6d216ebb5ae5ec31f64acef0debc10ecdb81bac3653ade11fddcaad628f1a69d8fad58e92557a9a819ec1d531cf05019',
'0xb14fb947da2757d339412e7734de60f1fee8114967db8db2e391e0e63cc39b00f889f5cfbd8deab0426bdd69cdf90363',
'0x36cfe6e700fc326a785c54d576853d0030f2b2dd4de26f8588473e5988941350ab1c194d0e79d659ac7aaec00ae31f63',
'0x64c5cb4973f1766261ca8ad4d8d0c83a2947f7fd9c7102fbdb174d0cf1f1b51f8f036a018908fcf27e442bc52f1d221c',
'0x2cff384f03f965d848faf5eb2963b92224db6d1f8154e27195efde4ff08aaeabc33a7bd5a29cd8117e5d7aa370892849',
'0x4e9f11f98bb1e6b363b4110ebded8814b7ed24b58fa84101189a0eaa79af33d6b388c11dcb0bb3955eb8057118e3ffbc',
'0x7c5e7ba9e4969bcef99e96a8e7755522abca791a8ac0b396e650af5193f05510c29b7c0956052dc0c29671b0907aee7b',
'0xf48dc95c95fe99564761fd42a2ca228158f2f37b4a32f8e69cd7b44c5bf4e7eacec3642940711d85fc53a68b487d675a',
'0x2217176fdd509db05800644cd0ac199faa8d63d757f3eb810d3b0b2bfb69f16f650e7de3cc0111fb8f47aecf2cd566fc',
'0x3ccb100931dce46e04442dc46b1bf9e67b09c80bb3ee1ab6ae1db273b4903718e87a7342cec967f55843e1bf3433ff16',
'0xd0d5e0bb29b2eeee17f457ecb727cb2f10cefd750c1416aec4ec0b26ae2d403bde7eebc45b1e8dde34e1fd29ee947f06',
'0x4d366427a9037260b60e8a1479c9e324a60a653787089df6df4efd39099f7e2052ebf61d016012aa0a09cac7a5531be4',
'0x486099a199ba8f0815f5ae2a90d65628eb2b185bc0f3424a4f58b7c20c511287178fca1fe886414e00b566db890281e2',
'0xf9addc3e03b8434d6ccec4b4d51b4a5335760d4b1b81f451fc842dc79cabf94d4b98a5c99d8ce6a42aa9550bda2935df',
'0xdeff1f164de4ed5293520788474993053133df97eac204a4feeeb0e1fc65fc40d364ed4eb13c994ac89b4184d89df1ef',
'0x8cca00d7f507b526a0e2c391e66fc1bd28023c720b1a02251c0604b9ac95d281b154f27eb1245665fb6bd92633062269',
'0x9cabae59065b4c1d0561b69d93c2f9f4509ea76098fa739eecc603222cef9172177351dfbb6533e42340925912696b40',
'0x4dc394b71d909379c9f2c0f5046185df935743c2540870e60db3df884a6a0aee7d3e86ac90db33767d18595b97aebbaf',
'0x4539068a82393c23da87fb369d72fe475c1397f8ac48b97ce2370fb45e7a77f7816539fc8de08c1bf21241cc9fbbdefb',
'0x24d54fa35d9c8d97af2521442a90ca8838f816c385195a39b5efcd5e1fe0e01225a4702319f20122d280a70eb800ea7f',
'0x55dfe83c8700bc292b5b815fe8f9dfec9d467a4f365b4f1b86be7f4012fa77de2867c431c2c2a17498c971edc35c8e3a',
'0xbaa2e166bffd06d4ac4614aff2becd093c6afc20541840ebbfa33bc4ce7b54c09896ab61cac0630ca36faa9efc5e9946',
'0x6b31985f33d53c6294533c2b9b33ae07e44576fea58c7f95b099ddda0514d53236e3f5aba552cea18b7ec383b5e034dd',
'0x30f60fdcc50c502c8ae46ed7714709e44f9c279aedaa1c63637b10e6a8d404c4b96a7728bafe0cb98ab33464c3c8bf51',
'0x77997ccb78a41ff861c07b8ded3c259b38f66c7df03665b9aae1bc471139ae830e0e304ea851950c5df5fe17e0c19a04',
'0xd2a0864dee8092f57664e74aa9dbf449137709f57c7c797ce15916ebe8b953ccd8579eddd33ae9711ff4214b0ebff3d3',
'0x0c4aecb2b8aaf80dfef4343ccf4c746495b5200ae391f44b7e457ac7019fe56daf989a040c20f666ce8e7b0973c6ea81',
'0xe9f022cbc409784377ac55cea014d8c50ef5785ba817eeca3a73d975f230b937fdc4c48f79c7bc0e6c0249b6ffb401dd',
'0x0b98a136e2d94c424e58a02f37ed29c7ccb3d0ef4a5d83fa11bac8f6fc8c9fd5fc469f2f5ca2ce3bbbac33d2a2e5d735',
'0x51299de1b2b6ca6947bf289f4d272b580459d534d861a0f8c1adc1c2276ee237e1ba364507d3ce0f559a2190432f7ea6',
'0x90a3d0586d6410ee795cba00b2302875610233c432a27bb81575756ceff6f3569ee97c809a37e8aa5aff6c88aa620c2f',
'0xc942cd6b0f2a10299605839c011d6bd2d3d8a9a2b15fee442387d047669043b3c7c719ec016f20ef86d89b292ec29769',
'0x7d96fbfac68545f31ca52c318dae2a1faba66afe8fc1e79ec7f5ed44f0f76c39f0ff1b5e070be5075c7c7259774999ec',
'0x532907d6654f2a749f7622cb42c5655403f30651e6539712c07f2ff6f4bd5dfbf5d7293697af5baf86837026f25c49b7',
'0xd51cc6ab8a7e7ad604092517d30a4cb645d13617992a18d5e9e205c177b8e951a38f7ae54dd7670e24d7672f3e966295',
'0xe1bb28f6a23635cc7ff25bc9eafff4b4493b2f68d681064285d074cbdd6a8368fea4a9294ad40515c9fbf1c417a6fe96',
'0x5974d0a66c4c1a8dd95660930ebd1b501e9adfd73027044c5eddff2a0ac244268bb4b4961ee81ff150991824afe84f03',
'0xdf3808bd77b7542219b422b6cadcf9812eea0bdbeaf9b6c06178cf9aca27a18a6ef9624d7f596c01bc7ae24626b1522e',
'0x6259631da9ed58e2ba3bfb71bca61e0d4b13661cf90edd295a5d755f1bea8e6c85519132d8cebfed0d168e85f439751b',
'0xdfb9f15944d5ea20467988e32d058eb611bb9039a929b48b3dcab6a5ac2b31d009966be7922141f52c1f758936a4e345',
'0x232c289ab6d007cece908a7d106fbf11812fea9d8968e4c8588709181a906f0612d2bb389a6c1995c2624876744cc44b',
'0xc6740c4adeba30389bea1ddd57c2efef199e203da4b59362a0a9b7305a853e4ba1c5490584fc2e779ad3ad8ee0ad9d9f',
'0xa73e0239dbe535e8e5a38f1d44fd51bd3d167764c1269ebb0fc460e9a38bf4521c5e339f16c1fef30b24a20a11c26494',
'0x47332839a62598a3388887dfaa7951f0ed5f1d05a04d277641d7b45181cbd7e2a812a9c8bbec972d93eda75a9af8db2e',
'0x840b1cc067c2c93b9a7ba86ed7373d6047a899bf1c5ddc9bff8803ed859e71dc22189760d158f7a1ed632ff8b74b59da',
'0x7684e6e6a50fbef4632e37dd3f6a1ed80bdc5b6553a4afbbf449fbf426f669bfd67bc67f6fb3eb72a374cfd52c809298',
'0xc612281f47b5063f67f3a988be319f7eee2c36c99d133feb90848eb70950dccbd86c0984dfdfa5b82a33d18f929da235',
'0xb635f5a186e85572eacdb031d6b62aeb6f5d747b86de8dc6a5e933cd470ad5045b6c6e69b5539f4cf96c257b881adad6',
'0xea8a1ff044387fc1e7b45f01562c7694712ce60b9a9c031ccad5cba9ef437708a58ca86c1629210de956e86843a6c67e',
'0xaf1f5c21e6270ba27a9e0a38220fdb28b4ef0b0988fdc6fb509e1c2fe509d90eed1716c9b07a9ffb681406bdd02a35a6',
'0xe29459ab50be04743e16ed97e02f87ac3bd1e8acb473d4bd595988722093ee9837f982799c9e52cd131729a970ff9232',
'0xea3effed14fce28db8d9230c0267861a29f7388199ba95560ae5887837cad1b858ec37c7655ba1598a56c3bc66250282',
'0x8a0dd4553861fc6db8c4273e52d7fd372f67c91a9a0570453eba608909b5c0297edefc59324bf428ae3b8129088ac96b',
'0x69fb1dedf3f6b0531f88917557de2f46d9d2006cb596cf52ac8c560b334895763d16dcfbd3459cf17defb652fd864bd4',
'0xfe5d7afe023220f910457aac491c24092cf7c0f8a470a55f7a73f9eeb07ffc3fd7ae0e862aeb3e2f5132670687a9861d',
'0x004ccf160df9a3fee7e0c1a7f1242fab264c352b0b774ca1d762786ff72f4953463204fad8faee016c6e4533e5a9de9b',
'0xfdb61b981cd3b39de1246067767cc9c0f3667328b5b430aa206a076aeb41731529958af96f88a0e045ef87e91d2bd4c9',
'0x409d9febef9c571ad45276b466be813ae8e363492f0208221937eb2fea0965f5d631b4eab9b9cdae3ee867c45eceaee6',
'0xf8fc163f06e2e5ed764a78194193dfa8b44b3ec41a1119077fd7d598987f83a20a8378a7bae0504feeec92fc841e859a',
'0xb85fd4732dc79916c5fe8c03b245b1bc85b2a3e2da38822f3d27fc87f6f54c925e7a413423031ffefa104906e7702963',
'0xb0f87faa6b1a5315338d8d27ee62f0695e77dcd7dc996bdd12b7d9556738e71559785ccddcfac02d31b6d79b07b6aedd',
'0x49959175fd5c7086d1e0157454e63fd6ba2693d683978339bdebb9bb486b073ea77ea2f4e33ccc35fc33367eeec20371',
'0xa1fbf7b73f2aba3eb7119f473a716be801f2899a595034228beccf0c6e2752cba4db3ad3d7fdd7f2534bf94bf7572915',
'0xf9830a160aaaa67792d9d6bea092c3f5632531614297ea9728d1d2df8267115d8ab83d8a7035a377d6b56a4393213def',
'0x93179922bd06a768a7d5fde944b5afa69eb35ac04bbe6e0ca3c2cb3f3e1af9391f26d75ed0f923e07df1289d22f6fafd',
'0xca52db888870985ccecd5cbc67fe1ff4a651e41553bb1c95110ea178e2212caf3f9d0170fbcf64ff9858fa879006d8ee',
'0x0f36f1313127227ea8c69bd7dcdb2047a8cfb79cb08d4fd94e4151975eaf9996f8013b8078fe0c13fb596e1f7b55c7a4',
'0xfe8fb57394e888e65b2e88b0df5f8d08477d6b58cf766fcfc323faabb218537ca9042b8add6ddae98cb2e4d025d11d6e',
'0xbc229513e891f3f7a917f6d127d73aacbc982e75b3ba3ac5ceaec1c334158d8542ee8b48fa2d2a463c93111eeb692a42',
'0x53485ef2b96edab23112e7812c1a3c68b0c61edea4aa34c016315b0bf2618910a1c041f6a3d46831ca95a0510b189ad1',
'0xdd8e87fb9a810e21922947122f42512c4839897064aec3205debcf060134ecb3648e545262881c5ed96556ebe201dd06',
'0x67beea359c078e1f21b6caed0289019283a4bc8efac1c79a3d374b1beb34f671fa41ccc44f0296bca91d0de55e4b3868',
'0x1f6979f1ea5270f0252a9f60a514d53ddac7cff82414f957d736ce901aa5204ac47906e99e1b89901b4a2273ebc13d64',
'0x3dbb2c6585ccd74587509ddd439547806fb2bbc8074add78f528e7949c6b67dfd1842c3b7009f35008e3ccf2b8b9f20d',
'0xf0bffa826c58c6fac4285dba3725b8222af36e116180821dda4cac94d442a6b96ae372644abdf9d69bdcd75508067ed4',
'0x1b3fb2490ec1909b409345f0da6764987629ee311614b348aef9a6bdf5ecea0c87a3c85f9b32736cde3bb9063f9514d8',
'0x754ee8823b495db373739fa84b03f2e506a0261f325fa5450d456afcdc324b2d8b52fd8961c144cc9f293c9ef94d5b20',
'0xb830e0b7273b0657bf6a1de5fe5b56af3121d55872658fb14c4fdd4e7016af5ab9d632c041047b4547c804fd9d7888bf',
'0xb591e8fc609d9705afaf6e06052f067e7f50f745faac2527d17680bb107f299db1dd92afd19763b424cc2068ed930dc3',
'0x10b794f8a6322808ca93be186a6b4a9ff20f4741836f2aee6879454582ed5ccc5439c9d175867b4046fe71e612fa827a',
'0xd7a98eaf3771dc0f9f97fb5d0a4138701bf1b680e5c93377d99856aaca1cede6043154c7a811e10a8af26a908cdc47ff',
'0x6b9564cb1575fab199b042f6c37877b8b8191f858c4e1377f2d37de2ff36e7c689956ea53b4fcd442b502c5ff5dc08dd',
'0x4117a6784a2bb9c1b2bb1d390f8ce7429695702cf9f24f528c5120ef0a48db1d50a55e53482983b3552b66005af3d26e',
'0x4e2d65c1c6fa5a0531152d2d66d0b59dea9bf94e46ba03144d59bc608eeb585b773c9c45fbe1b2804d7faecb4eda3eba',
'0xc4ca50d659401536166e14d1f5a38b605f5173b1ca147b4ac0f4ab59b62965ea6849c741bb3cdeba485e3b99c9130b9a',
'0x7976d4b92aad849a6aaf775e35131c22b0ebac15cf843ff49469be0f0e2a2f99fe79121505d08c5826e1352379ede2fa',
'0xb97c3028cd9d8e2996c51ce2613b779d87a141fb5ec0be65c81224471350ad136dac7c8523f7c931a9dbf938416b7605',
'0xe92f7e682edc6f8f2bbb73a678ef9a5202731f39949cf137bc95751b91320f6685a4575812ba923fcfb3c479ac48815c',
'0xa451996f517775a913f2f185c2ea97b175eb0c9720594bee84a88e0775075e4f868103cc52c96ca20960edd15cac48f6',
'0x51d40fcd47d2eec7bfd561814613d17e23b5e645596be6d5267e4c1036c50b023bb16c5aab8564f8abf022d3d17b7aa0',
'0xa8a068e4683dfc8af05b41a530a95147c82e4393719770ee2ade93f6bcda3079ee5904c1a0e2ac000d65ee5abe766c69',
'0x0bfab1fd82ab84a9dbd746f3fb22f9316849c695765bc0254bbad3b604cb008b85cb2a2e1d4cdf5f5d9274b9e9e826bd',
'0x6d301f3e1aa86e4d5c5d59df37c21fc6dad2b9dc85f092dd085f9bb2312ea7bc89805bee6364d81bf00ac5f3cf817c45',
'0x9c7057da473370af975509aa71f16c739c95d644373d07d790dffbb97914ab7490fe9ababbc778ee0d036395add36237',
'0x90a8341af13c22d3e8a7b0ba807d79f4532a6f03ff0df81a3d257c0f70d7952cdec98f3e17fba720be4e2cc1a6eeb8f7',
'0x6728efc805bb19b0ba6dc2510599a75e6d9637d636d8b5084a11953452bc662b35d39f2f2d32ebcea48b403c54575a6d',
'0xf6332e1c163ee0f34fa5bc86472f5ae287e8202bce9271525e58796319f64157554dd8c66a19d5521560e05f19a00fdd',
'0xdc23092dbd49c716f4391f0b0e8b8ef155104577882819cb08c0212895917b508958f6664ad1dd6b7ae7ed74c212da03',
'0x8562de6b280568079b656800bad68e499f564db94cd717dfdc2b5f62475a939987cb9366fba959f156d49eaca9677962',
'0xa34b1b6bc5fed41a3d4264c01e615ef5b6631277c5becb337bcb837da0d8a6e57011ea5d866a4c8b05301440086dc5a6',
'0xb841654cc9f4df24de63d70d82ceb17041ad88b5b99028c4420307673a4726b6808b2a476e55436d12c6d3ba7aff5a77',
'0xc8a9ee49f1fb7613a50fba677395a05c3391e023a64c7b8de073e0dde7ef14a66ced90bf0ff31f8d14cd656c56cafb81',
'0xb523e6577536500b57d307021de7fcc54a92ded489331e35f15c0730ee39f46ecc165643ddcc72ef417414d4f8dce252',
'0xe6d28feb3da2996bdfb5721d08ba8065e56b5a7d399b56c60cd80c2e0b1e466e7da4628f6b3ff15e267e0a0ce9959a14',
'0x5be620c88a7b0e164d7ad07561528bfffeecd2966271a86263f603dc5d76b02a058a0bae8d9ff26d60d28125693c3ae8',
'0x8b43840c8eea6c73e41d2838141e08e93a74a59fc3530777621d95cf6ad4d2f1246a9be76b0d1d87b025f76fb2984002',
'0xbb40dd570c1f3d4680a9b72990fc62d30da52cab7a799f6cf108d8ce0a1756a6d990674f814f95ce2f3d5cec2c7cfb03',
'0x6092f4dcf46789c86f1ffbef8c2bb078e08ef9e7e102baf05600e14574f785bf9280e6fd632286e154a6092356547bbd',
'0xc55452456ab909b66d6e4017b11144746ba52650eabb4849d407c39a65312eff74b2b3b800b5a7d128196da6a68761b3',
'0xb495216230a1b18970c7f201ed4ec510856ae4899187856bc665773ae1f6da864074570eb221a62a7db3f8f6d49de4cc',
'0x826d562555927762971bf8de30a29ad01a75cd28be9c7b2df6de521e105dc711d94d048673c99761b313890eb8f4273e',
'0x714392afa82df4be013d38baa0fcd0abe6a5e986a887175a11f074ee9d22960f24c1800969de1186039de8c081138122',
'0x92ee3bf65b7038031d9a69d321e710ea322b72a9d1bb6690d07eedfc82e1ca2e26836391c8282a8bea1758c4ff5b7410',
'0x7f5cab118f5e6c786b10d4e1822f95b18cb94401c9270dbedf5f2b609041465c80ae324000d535060b81157e448ee028',
'0x59121f848b10238f1d364903223ad08a9d30b79301a57d128849e06d3657d1f78111c6d819499a1e276b96517883a81f',
'0x0a54e489c700696c8a2a05f1e805c5c0a50d53a2b34712edaab8d2fc8f022e84b4654313d3ce3a7fab253abfb67f5d28',
'0xbcda68e89a6a593ad76d0d706890641f181c9343f77523815ca016a78c7ef8e48046433344995faab7dd6c04f8ca4884',
'0xfca342d905bff8984f2244647f3a187934309bf7d22e8957438305516fb837674f86a046898d44043976277007d80c10',
'0x27441aeec4563354a8a74e7a92cd8bc72cf823d4a88a764cde4ef75fee34a5f20d5194822673dc48c21016399377cfb6',
'0xb7a3c5bd44602e8191a311a7d21916c26d9d694277c71521daf9a0701ccbba8e84fc013e17243ee018e3726b284ff09f',
'0x6798ef7a65cece6370b40881d589b1ae4bbdf8cc7c90dcb862057e7edec4fcb0542fa80ffb49f06fa7658360b9a7398e',
'0xd510c3aeeb8d0e07677905658f02acb5a0425d4f5005bb7c8f8f2f7c1778a2a482d50cd259ba6eb16c589ac34b2eb070',
'0x6287c5c470537e2d6cf1957fc51278658ae72085e80d4ef00fb47ad32751aae4070dd18e5a33844224ce7024e427467e',
'0xfa13eac13f9c508aefd4ce73f769378f383640470976bd79c967c8d9269c733df03e3870421bf52a426ee9b25439f078',
'0x1a2794f57b05b1de0a7f2f099fc0f691c9dc61a1148bd2a9fab8aa58e18a4a133b4963097ae183ebc3116b1d9ac5d3c4',
'0x395ad067fdfd1981cc06db86bfc380ec98f9e5c7e60d25012c4033750e9c4bec12ff6bba92c411b5d00990feecb5ed99',
'0x124980b137fe45a481f9c2bef003739c6af7097e9cb097ffad0ed383c66488c51801471cd1b7e2bf8d941f848086925b',
'0x909b5d2eb82d7d88cda3f0e832c0f2a4dbb8184c85289e792bec215accd9741899d8f456fb85636920334957a9663854',
'0x674680e6609d07e48b36879cac2cc87f6ded19434f8afb2f6a2e08912867d9119e76b942180c4b39d2cba2c822795685',
'0x09a99fe8efe71b63ebae1624e6d7a996ed5cd25139cd945aaa7cdc561c5b2362100caa922c0f8ad58350e0773ef15fba',
'0xc88e21dbe2469db37627e1e6ea1196aed5c553c0e7b4d33fc090122434537dcb7e67b7cb8e3d099d8cc4d97bb71b7d66',
'0x37e52407730307a048f180ea71f566c499d479c48e9cf8e2e7ad664be41e3d8a3d8aadc362495979162bbc063733dc86',
'0x186d9bd71fb2d1b076888d48ce02d91b4241ce387af866e3a03e53e2336d455074b93f997e967b9c205f2c0d51bedd5b',
'0xf694b6f4e98f33bfb78e539b882bc48563278733997150d18f84e320a2ee9e89a0036971b186bb3ed6661df65a6a1c2f',
'0xf52ac5be3d13f7e53324124d1c7fde7b0eeca0c6484e0416d5161a3eda50376187349b4e4e39e9a47aed82e5e5b68925',
'0xda99566d2d7b744649d4545d151a53bd8b7f735091557b6428ba2d27247a870d225f6e86037b916fb4cb9ce64ad1805e',
'0xb9ce9f681055f581998cf7b79c60235dfc6778d03bd8aaea112962c608d63a52f9794f11109dcb0b0486c8a865e58e66',
'0xc07a85c6345937181e84632097b7034563fb01847a85cdbca7b8300ee15d661ce28a5e270f8d5f4ce6aced0148fd8a71',
'0xc0de0853cda433426c49a13357b5997705ca3f9aa145c7d987b11b1c7beba5248390c03ed63b96a11a59de5811910245',
'0x68e14b1a35b60cc559cc45411e363e640b733a02e315155d3770074d3f2ef54c9595b51cfd255f9552baf81f6a72fb73',
'0x32a07fc6f5ff0a956b71b2350c5e9de966b86f062a6d8feada2972afe06b0abbd32849114d0f3ed46bb592ab781b4a92',
'0xafc6052a25116b95fd50dbf1a4b135b1b36e6149187c80debdd056523d67ffb17ca2f5a7921e6189c2a6ad1fa4289d96',
'0x81cbc1ba3d7fa7176915f8703da9ff12f6e0837a2a2be4b60b66c12b77d0779faf7c0424dea5d22e1321b91f15a989ed',
'0xfcff917d3826a9b4477b6106f9f9b41e2349066581d56a743232a1c36d5c0a248e7173ef3950e09dd9d7c897e8f53e63',
'0xdcca77d4622d32cdeef6cd8a1d634479564a2fd08e80b62b72bc38726a4b19a46c5ce7139a2632e71b1dbd7ed0c1f6a9',
'0xe35f3bbd30fad57876fc696e8e3ddeb0215619fcd4b6a750c1b0ef4db314853f9e66810edadc95b85c957e2fd8275fdb',
'0x04d0aca517910c2bce86efb688c1fe6a4b1be33f66ccefa20c2a5a5bad49cfcd026208e24d73d127605ce58712fcb6a0',
'0xcdb6caf3ecde039dc94db845cbd1ea9d06400f26fb5bbba6bb219a01440f2bc34f61b3f652580273a3fac92e6af0a9b6',
'0x442e7ac1da8c81a2a70cc07674fc703258126aaa4f9f1e3f9c2d1575c6467e4b79f3a17f165de66e693aa2c18de8da50',
'0x19385775b2e878ebd1e3d22e655a42fb7cba4d9d0e8d13753296cb9de095ec1c8d14960962134037804af89f6cf305c6',
'0x28e94125577fee3f4aa5e1727650349d44833471bd9acc38b6fc7904efbb63cae7d5e34d39e3207402e7f77ab7f59aa5',
'0xd12d1a4c04c7e21328ca64108475f80424b05bba31112837e0f1c94b5dae1bd15b0892703e06cf5197934021d69f6e6e',
'0xf08148f4afeaa9a56991bd23e5216700b4b6a1aa38eb235f4277a86f9ac2dbfbe4263c621185f8a4b75aaded429d8e31',
'0x2287cfa4fe2b4c1b8f7fdd49a6c366b7946a8841950f9ccce3c357aa1faca220d566aff3726fc95954d7c24292c5a2db',
'0xe8d720e650fbeb848ba4433b259e580cd9034c5f77e7b8f3be9c08e1b5057886d17ad05f54923e51b68be740e0b8474b',
'0xc98c435317b04c2204563c462f625206e6673cf5e58c34fa54a69f3ab45980b48657eb79d13ee4e46d7a552d0ac9fa5f',
'0x38f72ed694ba76b1699fef27d2c5c4426d4ab4e9f564b9b9e9fa18f26e5a9476ba3bca267d8010b795331d4cc9741288',
'0x5ef1dff0cac9df5b13bd5302df715fa211f5c12b1cbb262d261a1201976f5afeacdee2bda3af4ac8d5afcf5ab7651906',
'0x4b0365c557f8d6287e30745b03ab2c7471194d2cda8d31b07b90d9c834712ac34f6269f8984b1e7f9684e9fc24b10262',
'0xfc073649af5afeee07775e253aef5dfd22245531ab88b6dff0b5ab68836f185d28d25010624b5cd266dc44da7c003e81',
'0x4e6ef10c39608508db111ce5165c89104c11dbad311afe933a97364dd8c4aeb54ec036c789a536a8540ae7a9c043ebd2',
'0xcf6769127dac036945223f2fcd95f4d57521fa0b43a4bbfcc479bcbe802db5511be797eb157383d83e5e48d4d5dedaad',
'0x82c37dc3e549046312d3258fbaef0d1a6bd61eb4e037a05fce98334e7fa79f047cc0c12ee9d11b9f4ab65efb9a8a5c39',
'0x367264280d80fc7ce21dc555e3cc71a7dc4647c21754073ea2c1f3be21f31ff28b246be4497d1988841e24e9e8802b44',
'0x81bee9e3a890d977e275da3914d9e15a120382b941077d46e4e4bc963f8fa3a3e84ce15f802b24e26e5ff77b23229e4e',
'0x882251e18a41e68124e71e41938e1dff7b2ba29425122622b7725bb4146fa0320ae430744b24bfdb00b5e8d3440f5004',
'0x95e025e11069f7193d042b53e476fcfeaf0382e118f90c8800ab9e61d1223b5138bfe4b072bfc0d2fd251dcf8d257bcf',
'0xffac11befb89e8096d185c5a3a432dacbd2dacaf98d5f9d14562bc0f9f0b6d19a2e700dba77a34e94ee97b424f292962',
'0xd5b13d2fbc8254ace9a7a0cd3e33e3af55ba3ab63b430aaa0e88d1b69d74ae6b1a1dfc47de844f3e67acf542f72a5482',
'0x678b253c893abba7ba28f0b28e23a1063049233fa69586a12b2c3dc4f0e35346aa5a43e821ccd4f30e1a79c8688fbb51',
'0xb8df1dbc0f4859fbb40265dd7757edd77638d418db8cc92e0616e80fc506853ba8a9750729a535750d0846409d825e74',
'0x89bfe60bc21d114a2a167929c09f590b73628cde8f40d113bc25e05516276cab5db61c0b2ab70c7eddfe56bc65ca169f',
'0x2354c6c6e1cef0ff5fdc1c00a4ca221ada0d41d51e58badce989014289f8f9cdb21e20b73a8442f392a33b1f74d7bfc7',
'0x2814ecf93fa58eb2d059de188f886ed6b7208383e21868d0d32ca75a42904dc69abddbc2b98072ee2f8a9ed1844e81d9',
'0xeaad5029a304373f319d3dfe5470fb2d3e4c84d03513dfca0b7487cb0f9d303c606a3297d6b1147fe5ffe3e84688da5f',
'0x2ae789db6bbf080527b46aaae8d537b930f7ee1086614cd6b6e070a5c4138fc74cdbbddefd7a35979b8ec4cfab7d056b',
'0x3aa1201e3eedd346c68d6db1415cf2cfb41ce52130d57f084f9dc7812742ea7fa4dd9935db73dc0590345b034e98581a',
'0xe033547be965ba6f0fd0c52f2c485258d291112fa2266cb01f7f8fd37c5971111a5cb7183d0bb8582f54f9a9935f84ad',
'0xb16e55028a57bdd38981393a5e685cb3ccd6b7255f407fe53eaf8824de430455506bcda9cf152843a6b59e705deea9f2',
'0x5f3fe192d534576a6f0daaf30861d33041ff6d201709a5625862700176bd7f34ba097b9a6ed978541a4bae38342f4c54',
'0xb37b189af6740319833b5117c293d99ec1c7f39dcbb5a856ee82304cf91553be8ebe6615c4dbb6716e0720447168d182',
'0x3a1ea4af20bf8fa9118cadf0624ee8fcbc00d48ef761d042ee5d75cddcfe25c3645d7cf1031814d8f267719b26040f0f',
'0x77d7956847c07a06be5352b2a4bf0d9199565ebfd57698a96dafff85e59d967617cda3b5718c0ab6a0966b80e788f59a',
'0xc2be68d9c6a0c36143291a29045f3a35172b33774ab1f5c0d8a9989c7b34c3d227474ff322a9760950fd1634f3d46b43',
'0xf1fa2bc34e9f934a43057f63c894682d9623266816e554b81b8090ee07d3cd6a17d0e956b560276fdc033265d92695a4',
'0x90a6f449e768fcbe9eee4e89eb8f3d40011bfe5e695eb0a0e1921351d26bfae9b8ecd2d8a655b4a24c0c7cc5c3924748',
'0x123f758baeda31cc4319d1f425037b83fbed631792f5f504165ce917088e86973cc159e01f546501d7c0afda22a7e559',
'0xaa1a3bed072ef8660788f85981fb38f1f699d344dc79b41aecb212d0ba5f81be6991b24c9874d1058826d4e646721643',
'0xe6b4009d820a6f8f375d8b7e034fb0e6e738ecbc26d45228f331a15c32cbca25687f453117e72cf654e68ab7581a8ae8',
'0xc64fed23558188237d013ff9b5f3923864e90d27e15403f6e7f95b5bd9e2fc98a6b1c49eb3ff9f10f6e9311e7ce8efef',
'0x250251f6456f6b645c1c03f77c0f8b7263b4543beacfce519b9a887f49e4aa391417c15e32a21d88c9e05edd2962fff4',
'0x9a7e2b9bfdae34c1c22c077489700dad8bd64e153086b8d86b26f8819160cc6fab60651047301efeac100c854222eb9c',
'0x70cc6c27e83e7ab3afead8384ae659dcac1c6ad282e64c38c068899700c66f3296ccdf41f08cbeeecf15e25cf7b55688',
'0xf902782b092363fff28d944c568d5ffdc03afb6250b6797fafd94fcc5805ccde0740d85451bd1e7908eae3e1732d3bc9',
'0x846b473960e59767dc6b90dd7661b57134f0ea30615a35f459e3f7dd30ca7de94d02aa3b704cc742421923d1c11af54a',
'0x5758f782e53614c0dfe8ea7f9bbb3cfb58318c67e4ac1d0e4dc235d3d34d0edb112009ed1d1656cb22b4901f9df5d59f',
'0x74075e911c10137bb591f8badfa1919a27d34e28768fc5f2f92e9c6b4663502f41c3c722a5c1cea17f71d55d6dd89f2b',
'0xac41ff579b77b37dc68df1a37093240c7265e608f4b6d9a53515a8eddb085feda760b1628ce993f2d7e20bb481d2ddab',
'0xeb7a5fc05b4eb86710fdd5046013f174783d7d03816bda7f75af8dac4e9093cf4fa5fe6312ebd2030c633e1d34d8fe05',
'0xfbec8a146a4e72c16a2a321d56bb3775ae8edd787db45eb8731c28cc91d11cae960f41897a044d2487532578e97ea25e',
'0x5df3f2de0d6f1416cc17a2289d56327e79a456d05d9556484aa200e240387173c9f7013ae04ed3a3a49535a93df0eea7',
'0xaab9b4c4bd9b9dc40fd583a0b91d4d5c97e7b489af172892f1d907841fa45b1e52ded3835f1002d641007739c454e2f5',
'0x6c5c8b3f171f029bc04da7df05aa3d0565480d2ffa10b530c913404cf13db5e3ca77471d4e22a14a4c43c68c39f8eef9',
'0x648bda9b2728589d2e112e4c486aed03fe141b6a1aa2acbeff11a3c1787bd67aad3b874f43ee7585a0d48ffb21986862',
'0x6c84a77e18c94bdd56f2c111028267b3dc66e1a3f20b97b564660c18f4019b3362afa6ec3be020d6a91720a5c06909c3',
'0x77b1c7b09251bba867688b85a7152935a796400dbd4b38278c115b3619045beada3ff85be3f30641390368455f350c0c',
'0xd6cb208742e27a9e748496b6e4c097ac05e9574a5077d141323dd75582224d43ec06d954e9f36b4dbcbd944bcf7e6a6c',
'0x1793279d2dccd1587e24c2efc3d8d707c3db1e9d219fc37ca86470f6977cad92fdf9cd12ce33b2d1ae9691881022edea',
'0x978f41c8d6b3b0bfdb74ede41cd4b491646e9336fd578d493fb372a8069e365c91c4831ded079fa7c80a1d5dc965492f',
'0x0eddb5437012e4658f1bc1a0d75275244c7a750b5c5a9e46061858c2fc9522a2e9e20ff93a72f89c79799bf6b384be2c',
'0x8772f4595f1841a78689e4b008a6c1e756cbfaefa0483adb45ce2137ed2cf5be11d0d5cd6361db8757635e9e433a7c82',
'0xef078fe5bf30c58bab979a91a4620e82d33e8b1b3bc72d314bbad6d555352cf3c56bd28344243ddf86bde0e16473a8b8',
'0xa957a24ebdb5675660996dc63fb968d333741622c73b659f8bd656f3ac423d180027a671d7af93de58632273716c6af0',
'0xba0710e7b364acc2b264e8b262f7710e1ad9f9f29b8492bdd6508e179c85edc886b9bc0ef31f61816533c98c38c51c6c',
'0x636cf791d0692a84083ed06481ba2296250c0b3b48ac4b7569d7ddc91957cf0e39da0edc3ddaad608a1168f3237bc2e7',
'0x71b9718a36bb5dda54751e2404f7c795a5a28da0c588fcffcce0fbc36c4f0180c1495e21acb2361f05881b5f5708a5d9',
'0xea90495da5c8223cd4ad3ca52c2a505896524517e2c89b5381ce9bd5bab5a0b0d68eb49117255877893f65c8bc0652d4',
'0x0871eff0508ddcb1b0e83b41c2e21f53a013e5ad0843709472f930c17336fd29aae02c8fac2d944cda8074f1f3e01fd6',
'0x11cd897ec95ed57983cc83fcdffa9a3db2a7961eaec7489ea17fbaf076b008259bf802b3ed4291df16c453e839107f11',
'0xada833bc0e3dd3f36af2b56b56d578d57e488c837fc9ecfa3f0de421598ad6b09498cf8997f11a70813207cb522b0a66',
'0x9f11b2f9ad12f13eed067c5e20b6537f5139798e1a0bfa9f0d04fb5a950276f1e30a623fb53b1cc0221270b2af58336b',
'0xf48f53ea461d7e436bc6db0c6aa019f54f72757c4f21111f35603b72713b6a9ad2913a58201d7a82264c2e693ae500af',
'0xc26176447a12fca654d580d2c6472f4162461c29167dac0b7e160595f371763b6a879bd60546811810accc81077e5f72',
'0x931bdb675f9b66d2da0e7d0822d3893041e8de98a5e9aaf588f7409d97dde19eda7f55e4fc323b21f1f3670ff81dc164',
'0x25ca8fd27533c3e31b0a0447be9339149732b9266defa1c1f93b957b6f86d1183604b792139e8d156dfda44f3b780f15',
'0x68686a8f91a8053a8f9abd8a239b1abf8763c425f4b88cc450466d1f28f4d5317a64604ca1210566fe4aaf233dfb3430',
'0x475be9828fede1fee751fc4dc339a33696d57dad4d9c5bb619ab272e7065660f677267cbfd2f62a6f8da31521be208b8',
'0x2f5af174b43b99c3ef09b7b074e64043f921c4e3f86f073fa7e5649e9fe8f532958390cbfb4f7b59701b506ff319d116',
'0x5c0602c75b2be5b5b637d6b87a7ad113b2103e05f40bafecaf61792890dae128ebba3d3814f71b9cd32de40b88376755',
'0xa23d8509dfb5327435b8ed961f764b71c0e263a9762d9fb1f3ffdeb1a9861c65545d4f961f7148d670f0014522d67760',
'0xf0035e147af8832792427b455ef2b65706aa6ff9f483303ac5b5aa591dfaaa94080051f3d7baf9c7fda91b77beda2398',
'0xba4100a949544d9cbb3435b93cb73460bc975a5b6cafc489de566136feb75b16f7bef46a7307b893608842cce3c9d37e',
'0x4a943e3f8f10e6a25baa84d45fe8c6c82a64d825abfbe6bcf9b895f591164d09676fd475473bfba2aaf57564d76fe3db',
'0xa004d45ce4a40cfd3f0df1dbb437fecdb3d6b438f0a60e8c75d866a4281bfb5e025c013664653dca8009c61ccbfa987f',
'0x238f2f8c5863bf1f5713af1fa0451ddc35d1962f73304bbca0f362c2fcca9e2030648fcdde961d35b9fd0027c5c5304e',
'0x589e10a0265aec3eb9e056d97a8f8acdbf945b5255b81f0952a445485d94c6c9c713eb95222e50c0acc23f175ad81d2a',
'0xbe04266411adcaa3814d08dba82198af233c8db23d0e4a10c4a6241904859308675ffd60252fd3e2abf2593f10c33d57',
'0x9123e45d95a48a6c16583a9dd235f4d285b2e0ece9a854f923528cda0a4490775e6b9f8dd4af1cf89a253cb4040e84cf',
'0x2a35faf9b3bde4f5749b437b3e50e3da4464e2ca5f044749d016532d334c82307800e91e8ec3bb75acc088e3a100da8b',
'0x54b9b063ed332757632678fc27b964f0b620003ae9aa5ec66a8b3b1ee7bf6853495e6090addceb65c65034b4ff9a4a95',
'0x54f0f6416efc66b213ecea06d777ac15417b31d98b5d41e2114db8540903397bf8f1f4b6995d00eed7f6fb17e31162de',
'0x1c4d97b73ce0c40ccf81a3755ab21aca0bca5f1bf0b8ca7f8bca5c10e57fe1086d4666ef80e9a7faad165ddad94dd61b',
'0xdaceee0743e4a8898d5083e55f2fddcda8eeb3e98930223737a532e258e13c64ee08f2e8919c4a1fb06a77a951d90cc7',
'0x16e77ab5fec85fc7ca8ca1f22eedb7d526bedf7c2118365df751bfa6d74f7b7c49031c679be29a0b4b8bbc6601f946c6',
'0x5a3f0887e947116a43e472426e73b04f459f4d22eae4e53bc0fe8e2ea1561c93417efc909451264ea97d271ba2f3b4bf',
'0xf8841d50c8149a2da4135011c5d243c6a070a69782d825b0f15d5fe418cbcb5659f76ca4ea02f30d42a9fa51cbf2f438',
'0x9dd39a3f44bdcf1b8df92202049b3f9114d59964d75d838bdd0011e161d65c1b320b22df82e78cc1ded097738e44f943']
aggregate_pubkey: '0xfa65b9c18cdc09b0378365eef95bc7607f15c27ac655b0d36ab415eec9b86b611f9e4a91fde40ee41f9756e445b7bf60'
next_sync_committee_branch: ['0xdbfa8fa48e3d6ddc4f03f7eaf7a8c972d93874be3dc099f04321d9f0330b4a7a',
'0xcde5a0a137180564e55daeb791ff8e949f3ceec719fc888746db8114799e7907', '0x57186b3739879e9e38b6b6d3b2f2fcfa24096d6ac25159cc1683784eef086f1b',
'0xe76df4cfe62f0f0b96dc5e322e80cd334065d6614f38e845e77b6509b6e2ee9b', '0x9d5aa28dc6c6d1afeb21939199327ee29acbe91a1645676e5303be28a8014a49']
finalized_header:
beacon: {slot: 316625413450645564, proposer_index: 2235030417841148467, parent_root: '0x66db75258d10274764e7f831ab0b27a7d9f49f7f2a55df2d45ed4801c69c98cc',
state_root: '0x0feb208a5a8c6d921ae10c1aee15fef159d907f6f0d8fe5545140e555f746bb2',
body_root: '0xcb968a1a97415c40e9fffcb2afc34cf97a41f37dabe0a2135f54441652cc9f9d'}
execution: {parent_hash: '0x49d2ec2b01cbc3598871d4b94a593c88cee6001c92c553e02ff57e82d17d73b6',
fee_recipient: '0x265bed715e6f74f4771955b139fd724c7d1df097', state_root: '0xa0df6e2048c6e43e3b107945830c01e05ab8a6dec4868372e4e79245f1a48311',
receipts_root: '0x4ef33903d6d889498e02eab2aa567fad49b6a7be102b0d4818312c1dc156ea8f',
logs_bloom: '0xfdbf4a3cb1e5d8e80d6a2cb8ae2a9f14ef971f2e06f5f1098a46c4c74a9716459a47613969324eed0e4c322eaf879fe305d7dc5c34cbfbedd45512002495c48c10d151d0ade97fef3b201b236c4d1f01daed76a1056797d88cf1a0eda2626eff7c36a0d247d6e09dbb462a8bf43dad4bf399fbdb4527bc3fa1f3e79f97a2edf8d81358bf4667bf4e48fbb4690ea04b2abb1ef130ec4b3455533e5438d7fd4f86a2c334b69e3f9fac7fcf2d610a43479ec1629d61772c3e287b2cf4c255021970aeaafe0fac57446d31850a5e5b081b2d954d9b215f25179d002039f9946ef8657976ee61c6e344d7699075369ff6458c039aa49c9e85c15b4a04d7bbf9ec035b',
prev_randao: '0xdd8ee6fdcf52ed8fc64317bdb94ab1e144be7435915732770120003676d05da2',
block_number: 12693846000057116801, gas_limit: 6031842080012842514, gas_used: 9967122864728909438,
timestamp: 12159888462418683061, extra_data: '0x5ad3b8fa26fc1606d5705a0c27f9398773ea39e790c4bae298bbe85393',
base_fee_per_gas: '108163104809704766364965581702648976119844613916919271292869992592815948361249',
block_hash: '0x383b923e2819c9620a71d4eedf58f7ca9afe555bf785e754f00345c79c7252af',
transactions_root: '0x87da047a9a6f2bfb218d17bc1129cb0ea361febe2c01e65ddfae03f0fc908d3d',
withdrawals_root: '0xf9c686992daa952349ec70a44bf14238135b25c788f1286d8c5cb7aa8d43b94b'}
execution_branch: ['0x0c921fe9c8f7818d508ff6fb77c65ec143f1822ae520a63c4ebf8a80f11d5456',
'0x6c3c2ff89e639832cb9af9681632ba4c405da672201d4d9535d37ca197c22d22', '0xb59f7f49d34041a2f7094997ce314b0a65d2e67dfcdb24adeeea3f935eb60bb1',
'0x1632b37b4b6c59e99f6b664da30fe4b89c65129ab895dd6d8680175fed9c3dff']
finality_branch: ['0x4fba794addb5d9a72464494236a7d571d4291bce2e03d64bec942f1fc72ab0df',
'0x8fe1b574d242c16172060107445ad3480728bd5f2694725c04069744cebe29fa', '0x6bd23af65c583ff28885e244d7d2be9176e17aa01372769d2269be45b21a9b6b',
'0xbe7605bada7f83d5026c7b279109b1e4c88db9ba44c023554c90c5ba50bbb069', '0x0b5d4d542aaaf619249dc8ae66fd3d02c4b13fe4917cc674875523a36f415367',
'0xa1b7975ad17d84757d6cae98eaf4f3e72c15b348d55b82c27ce07e5b681fe6df']
sync_aggregate: {sync_committee_bits: '0xae3d331070bcae33c4965e6ab550e7e526aac5a94fe075f6b0360e5ec16ddaa63dd240e5e61bb659a1b57950452d984b4d5ae63685b2bce675a7c6de35605a67',
sync_committee_signature: '0x7d280e88d4358bcd312e9ccd894fef1dfeaf46e4a081ea07971779238a3dd8ad2ba339954e464df0a4cbbbbbd22258bdf543ad6f51232ec48e2d151527909c084189480dd6e350d5e022621aceeb478ff215b9d0e8fcd4fc7dca8c963f70533a'}
signature_slot: 10239421204327532221

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
{"attested_header":{"beacon":{"slot":"7358726","proposer_index":"427162","parent_root":"0x1d7b8baa34c28a3e0d2230f8d459b92203324d1e1ec48e762c527b5ea7055612","state_root":"0x3b7be385013f0f43c12d2b5d42cedb6d7d18f854ce3b04853f526d0024034171","body_root":"0xb0bc8ce3ec58d241366aca78630803572d36ae24f9e9d52a4e21c6370585f60e"},"execution":{"parent_hash":"0x4ba3d43cc285b7774aca41a1cee5c4d4fda49e4d599065dc789c5e8d17d289ec","fee_recipient":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5","state_root":"0x7dfc406a2c6b07312e554a3a34b75ba153196559c83b60556b01362e0bf190f0","receipts_root":"0x8e4a047603c7618479f2c243c4cb88fe6329cc7c552e53bbf79c02114a2eda48","logs_bloom":"0x0fedfc2d69e98b6a31ea1f7fd9fa9a70bfb96841daa484d4c7fb60c8c610f5f6ba6541cc2124c3e157185f3362dc0b18f2338e12ac357e661c57f959f9ee2b27531d5018d6d8b9fbacebffabd694c7bde11929f04e7c28eceb7dbe47887c8ec4fe24ef2613b67998e72453c1f621a877338225703e18b73fbf24091b3a596c647fdec25df39615a342fdf06c4392ee3667b0e38309dfc7b9c77b1b4669f6f13a8e2b05573bc070842c1a70e9dd183faecc8ecd8ef93adaafc1472a4fd8faf5711e50856a32a10ad92e8db429dd6f10c14e6e6933c07daf14f804e17219a1e8071df964883b22a6f53d56e4a8ea05a94147e79682ab0d8a7e2778d9e16cddb547","prev_randao":"0xe7a8aa63bfd300810ebb9fb0389f25b2f83ad3f859a2b6d6e8d10b5e9e8a9f14","block_number":"18170142","gas_limit":"30000000","gas_used":"19938147","timestamp":"1695128735","extra_data":"0x6265617665726275696c642e6f7267","base_fee_per_gas":"18458742656","block_hash":"0x91a4a0d4a27a88f264320a82cf87743a6dc1ad724a1ebe74db8169ceb314a1d3","transactions_root":"0x094cfda3b6d80ffc84dd5ba017ef5b30a9e2e94125fcee4d42c9cc92bc2238e1","withdrawals_root":"0x4a865418c1b6acbc014b0545e4af40fa7e3ca51b44f8afd9f12225edc5a3f121"},"execution_branch":["0xa632d98f70c821de565a89b080491cfd28905c68c6bb9230ec337d3f9d255d65","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xbe0654721b70c4f82c3844f3877a123db4a49f8bcb417d6165faaccfbac17e99"]},"finalized_header":{"beacon":{"slot":"7358656","proposer_index":"584647","parent_root":"0x51bf31832358ee618a84c74193fdc1e58c810cdb006eaf82318edb4f2cfbc899","state_root":"0xca3dda0a7bcfade21f508a62562047dbfaccbf3b7b1e41e28177ff2f8ff6ae3f","body_root":"0xe5a8735e10bd19059a5af294fed39638eeb7344abf49d113bb25a2215d050f07"},"execution":{"parent_hash":"0x1a56b9cb321be8608d892a731fe44c829578888e3e67b38459e06d4b3885974f","fee_recipient":"0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97","state_root":"0x2f3491f8eb43e0b0f1c526e53b29df6889ad90ad2d4b72d3201ec91d2d21cf24","receipts_root":"0x753f312a70a37fa8d1e55960c5b8be9f044db12faffe6549732b4f55699ff334","logs_bloom":"0x49bd937641fd2a48b2de33e0a228ec2013b84fb4309c98ad604d0702681081901012286fc320ded140110f24449c159426252041a8a26a395d10d98ec368a9707e6b430e4f64b9796806760fd417182e95980df60c6908b4913358069074025c9f0c800c1686830b640450a603603e63e73221602a9976846fc00d98a01a281400e62345ca80811a04e5b08216981fd398408a81ed28810d141920e190b2c72f8ba00054513a2a523e0a44ec599a94e04cba54245429b18b8044a0f804022423cd04118a00222a0a24491a40d4a21204007000d0040820d42b23940205d0ec5f1c18ac48940022aa400500e469264c618020b2925f3f1a422c9018712bb11c43","prev_randao":"0x0374fa31d1b5afaba63b7acd2de8352da04cab41e493193958672f96bcda04b3","block_number":"18170072","gas_limit":"30000000","gas_used":"15671422","timestamp":"1695127895","extra_data":"0x546974616e2028746974616e6275696c6465722e78797a29","base_fee_per_gas":"13939250676","block_hash":"0xca87151eee53057062520f13077a9d11185ad4a9d0cce2a8b4a3aea71a6d2426","transactions_root":"0x00ea0f91f1f7e41b0cf9b65bb0be1d8f4efa66fbc1ede6e84b603f645aed94d4","withdrawals_root":"0xb4aa6ed2229d997ca83f077c745165294afe093126915e439a32644921cc9038"},"execution_branch":["0x728b804a08a074ccfb89211fb48537ad0d033139c6e85031e818560bba0101bc","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x6ef4b335fac95fd2c133a0c7f54f730fb05c60eda8003ecbb62f99b5225c5d19"]},"finality_branch":["0x4682030000000000000000000000000000000000000000000000000000000000","0x43b3c8708ddb8f57a75a131a951a4369a869a3b157ee7b8436196cf18fceb182","0x09e07670197a4694452b40964340029be53e70cedc772b1403943bb170b9e723","0x2b8a2b2c9383dd30341d90baa00d5e111de55a64a5fb8a33efcd43a07d3ffdb1","0xc58b9ac49bfe4b0f9776e91e3c29a18545a214a3da57fe7fae0d1949f4377878","0x863f1e7fff7ab79747f064586436cb3010817ef1e50a70b7fd7ef773b2af701b"],"sync_aggregate":{"sync_committee_bits":"0xfffffffffffff7dfffffbfdfffffff7ff7fffffffffffffffffffffffff6fffff7effffffffffffff7ffff7ffffff7fffe7fffffffffff7bffffff7fffffff7f","sync_committee_signature":"0x933b06c2d41a4e89b20dcc2d3313d420e9d1b975de79f4b89f0dffde5cf76f028356583ab5f6fa886a4b138b5855367c13c88d1d6851e2820296a800ec1b6d7bd5c6dd41037d11df3c160c96e843460d911f07f06b938a4e373e8f02f40e873b"},"signature_slot":"7358727"}

View file

@ -1 +0,0 @@
{"attested_header":{"beacon":{"slot":"7358726","proposer_index":"427162","parent_root":"0x1d7b8baa34c28a3e0d2230f8d459b92203324d1e1ec48e762c527b5ea7055612","state_root":"0x3b7be385013f0f43c12d2b5d42cedb6d7d18f854ce3b04853f526d0024034171","body_root":"0xb0bc8ce3ec58d241366aca78630803572d36ae24f9e9d52a4e21c6370585f60e"},"execution":{"parent_hash":"0x4ba3d43cc285b7774aca41a1cee5c4d4fda49e4d599065dc789c5e8d17d289ec","fee_recipient":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5","state_root":"0x7dfc406a2c6b07312e554a3a34b75ba153196559c83b60556b01362e0bf190f0","receipts_root":"0x8e4a047603c7618479f2c243c4cb88fe6329cc7c552e53bbf79c02114a2eda48","logs_bloom":"0x0fedfc2d69e98b6a31ea1f7fd9fa9a70bfb96841daa484d4c7fb60c8c610f5f6ba6541cc2124c3e157185f3362dc0b18f2338e12ac357e661c57f959f9ee2b27531d5018d6d8b9fbacebffabd694c7bde11929f04e7c28eceb7dbe47887c8ec4fe24ef2613b67998e72453c1f621a877338225703e18b73fbf24091b3a596c647fdec25df39615a342fdf06c4392ee3667b0e38309dfc7b9c77b1b4669f6f13a8e2b05573bc070842c1a70e9dd183faecc8ecd8ef93adaafc1472a4fd8faf5711e50856a32a10ad92e8db429dd6f10c14e6e6933c07daf14f804e17219a1e8071df964883b22a6f53d56e4a8ea05a94147e79682ab0d8a7e2778d9e16cddb547","prev_randao":"0xe7a8aa63bfd300810ebb9fb0389f25b2f83ad3f859a2b6d6e8d10b5e9e8a9f14","block_number":"18170142","gas_limit":"30000000","gas_used":"19938147","timestamp":"1695128735","extra_data":"0x6265617665726275696c642e6f7267","base_fee_per_gas":"18458742656","block_hash":"0x91a4a0d4a27a88f264320a82cf87743a6dc1ad724a1ebe74db8169ceb314a1d3","transactions_root":"0x094cfda3b6d80ffc84dd5ba017ef5b30a9e2e94125fcee4d42c9cc92bc2238e1","withdrawals_root":"0x4a865418c1b6acbc014b0545e4af40fa7e3ca51b44f8afd9f12225edc5a3f121"},"execution_branch":["0xa632d98f70c821de565a89b080491cfd28905c68c6bb9230ec337d3f9d255d65","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xbe0654721b70c4f82c3844f3877a123db4a49f8bcb417d6165faaccfbac17e99"]},"sync_aggregate":{"sync_committee_bits":"0xfffffffffffff7dfffffbfdfffffff7ff7fffffffffffffffffffffffff6fffff7effffffffffffff7ffff7ffffff7fffe7fffffffffff7bffffff7fffffff7f","sync_committee_signature":"0x933b06c2d41a4e89b20dcc2d3313d420e9d1b975de79f4b89f0dffde5cf76f028356583ab5f6fa886a4b138b5855367c13c88d1d6851e2820296a800ec1b6d7bd5c6dd41037d11df3c160c96e843460d911f07f06b938a4e373e8f02f40e873b"},"signature_slot":"7358727"}

File diff suppressed because one or more lines are too long

View file

@ -1,13 +0,0 @@
# Beacon state is sourced from consensus-spec-tests: https://github.com/ethereum/consensus-spec-tests/blob/7ffca8d9ec30d937bef52097be6715419e93a4ba/tests/mainnet/deneb/ssz_static/BeaconState/ssz_random/case_0/value.yaml
content_key: "0x14ae7e346485874006"
# The SSZ content is prefixed with the mainnet forkdigest
content_value: "0x6a95a1a9ae7e346485874006ac0000006f4ea1cbdfedfaf60000000000000000000000000000000000000000000000008fa1cfc27730c1942e4919e2a8bd2668e5ba02ee7769599a931db20dfeb7120cdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d719cc5feb29e02a89a7429376ff532621801239f408e7f7e63049a771d5b6473dc5a3afe60235feefc3e97234390b780e1996cbee7439111ca287fa001a9e1cb7537c8d6fb365cbcb3c68b958881578ef49bb8b51440c6b934e8e9dc8366c9f4136bba5413b4948f8015a1abe9e3df190a0d0ba524facfa9340f310a5af718d93311dd263279526c4f8575a52cc6eace34090dbd6d534ab44fad8351757dea160a993f8d25ef03a1096a86bcc10371a3f0b83d1f322532723202f021e1b95f2d5060a6e9eb9c8d35d5c9644fea283bfd553fddf6613069050a4c1ac38047df75aa3fa064c5de29b76edf1cde4fde3ac58d203d7ba87165cbb542fb7a7967565929"
beacon_state_root: "0xf596255a2b920a0387e770320ae4533a221155989f0e8f0a9091b19fef97d279"
historical_summaries_root: "0x696023d8205c3a8ff5d5a880bd6e343e2f1fd95b4c8d5d85d693b54f71b49265"
historical_summaries_state_proof:
- "0x6f4ea1cbdfedfaf6000000000000000000000000000000000000000000000000"
- "0x8fa1cfc27730c1942e4919e2a8bd2668e5ba02ee7769599a931db20dfeb7120c"
- "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
- "0x9cc5feb29e02a89a7429376ff532621801239f408e7f7e63049a771d5b6473dc"
- "0x5a3afe60235feefc3e97234390b780e1996cbee7439111ca287fa001a9e1cb75"
epoch: 450508969718611630

File diff suppressed because one or more lines are too long

View file

@ -1,6 +0,0 @@
{
"6718463": {
"content_key": "0x12a083660000000000",
"content_value": "0xbba4da9670010000b30400001d34030000000000000000000000000000000000000000000000000000000000a4f8b2415bbca66d73597343afead504bd8282523df27153543b2de8973b7474ace652bb73991c3b63f7185a17333c8aea619a69b69863a5d79810dad6c363d3df460765de20d3c8f56bcd9490f15e2d9e122b3f6173cea76f6d0d16074ce0f0ecde5df1b81f36c08e91ce803c25add312bfd8cb759a81b7cfd158d724959cf22e83908e043f77bdc5992806617ef1fa0a4f1985d9aa1b67371d3580be8c2c64ffffffffffffffffffffffffffffbfffff7fffffffffffffffffffffffffffffffffffffffffffffffffffdefffffffffffffffffffeffffffffffffffffefffa480f81d481c5aa8439f8c65f86c69d3b1570cfd95b61300f7e29c5b0954c0c0fb080bba0e8ba60dead40c45355332ed06982fcaf53ad1ad4a7e6e39091ee7e5602d4a30b6d46e12047c31451f246bf43c4a3fbdb1d1de2f2180a776ef71fc3d0084660000000000ff83660000000000e5f4020000000000007b8211614b9c5331a1de2691ad7cdde1ca113b4e51b9f2757a4b502833f9234a06eb807d82b9175dd085748ade76aaa86fb4eca48bc8deb01b9c56a267a896d98469820ef7d34f8610e134c6eaf8d379e83cf6c3ab0663fba828edab344dc5f40000008e2a532cfdfdce86b27b26f611f1df0e2b00a54d18c6980e69ad28a4b70bf480336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71b4c4e01065cd22a0d2f27a544ff8315b9e28f6270a63f543831778f32648ac3a15ecbea44f91b9b4a28ec8308a30a4b6252f554a2088bb1c73ea01013ba4963dfeebabe6b0418ec13b30aadf129f5dcdd4f70ceaa56499df0b3ba9d8fe6ca410132a29cf387371294137003adb139ce9f06b6f7dbf32ee01da91016a485a8aab5669168efc3d3a200b2ee0f25d937a413dfc38e44e211f02432a01a83088e700808c302002139aad8496010300cd220b14288429d8a6049945000a3b60f00f00109019341a0129a08906314848650817002cb98894a328084521a87e6d0a64e8080004ac0805000800e29808294016508a2004001304002a02320c4705501ac008a62848701040012a08e5c8b69010f1009f80408894857053e1b940e2c9004912860e26112080610d002098242000ca001420209a42c141d0c86087509344ced8a32e0204410a0196025a12106104e20008ad4449d5043208090008810201820209108607a0fa1004aa323410050222867024e0d172a82821b02c00020513152006484c800b10f0420802c0244d0850288c6541dc34cabc32f5d355aef4017425cab95c1de0474d483623e23c9bf13d9a18f1597e930b010000000080c3c901000000000ca88800000000004b6094640000000038020000ab9bf66d04000000000000000000000000000000000000000000000000000000797be971185ac699fa7288c87457c3611b443fb98483fddf282dfa7c5b4b27058eddbcd8b5de9b747c57c827ceba820d19e987ff6740eed38ccbc947696d71e3e2c3b96c9200d7d5c66fb4e8458067d6420e64647682ca2f0798ae817d2bc19468747470733a2f2f6574682d6275696c6465722e636f6da08366000000000037b10700000000002a7315c8ddfc25dc2266a6b221cb8f9fdf641970ab1f65a2754df4e14c432b9c446c604131913bed45976c4a8ea27df843a72d622f80ef15da622f0d56ec1ffe3021190177b0405c4fa1a0311a493c4dd095f24d386be6a03f5838a6b1008665f4000000f5c98fc152bf40e1ce216c8839b8ddd42ea5b355f0b5c700fc9b2cb7c802e1c8336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71a533e05d648cb9635382f38b778dc3c76b18e4eab2d017fdb24c1c0e32d2991707a45ce3f26e443dafa697a76bf24122c9b19b57eebe798668dbd1a0da7c900795222290dd7278aa3ddd389cc1e1d165cc4bafe5c658153cdef5850f97c4d4fb6706b82310414a00fe7dc34b3043e0c7a8a24e810e3cec9de3e1dd7ce06bc191efd2273a357da4436fc4bf82069f6cb87d3d605aeda1336ac513f358b4296b8da51232896ad271063d93404eaa6b80960fa2d4b41d74a544eef94731f0b83ba2701e33b49e3191098c3369bc4658d82f536cb9d38285c51c7844292ced06dd2e8148f0bcd585dc1104e64aa930045e83d828ab436a0e587637e5ef83044d1c2918da284d7072847132885d443640979907485847368e32501345b0dc24c904c06732cc17d944b26181b0c05c6461bccbe4f7b2180a282b6871aa6472fa36cdc49ec50cf69d3eee4b26fe02bf48f39efb549d3963d19e4112c7250c580543516204ff1b0675680cfea02ac09f0b23d6e28c14b08421b3a10a9788442457d732b120c5c466c54ad938dedd3cd3701d9b32fa15dc8db3d6b1ea5d8495732477a72b34efcaf8c349aa40d8e1f21f226ca11182cff00921930b010000000080c3c901000000009104e90000000000d75b9464000000003802000076ab1c8604000000000000000000000000000000000000000000000000000000844f7a7e7585362114fc88a70654146215ab4eaad65ad54b64975840b78d365e8b75e78616a92294e747589c3ee4a845c2962e1cf6cd7fe8bfbd09439e5e6d5e4d4efdf181473803e734d97891388e1b50628658ac818599cf2cb069c8e6b38b6265617665726275696c642e6f7267"
}
}

View file

@ -1,6 +0,0 @@
{
"6718463": {
"content_key": "0x130084660000000000",
"content_value": "0xbba4da96ac000000ffffffffffffffffffffffffffffbfffff7fffffffffffffffffffffffffffffffffffffffffffffffffffdefffffffffffffffffffeffffffffffffffffefffa480f81d481c5aa8439f8c65f86c69d3b1570cfd95b61300f7e29c5b0954c0c0fb080bba0e8ba60dead40c45355332ed06982fcaf53ad1ad4a7e6e39091ee7e5602d4a30b6d46e12047c31451f246bf43c4a3fbdb1d1de2f2180a776ef71fc3d0084660000000000ff83660000000000e5f4020000000000007b8211614b9c5331a1de2691ad7cdde1ca113b4e51b9f2757a4b502833f9234a06eb807d82b9175dd085748ade76aaa86fb4eca48bc8deb01b9c56a267a896d98469820ef7d34f8610e134c6eaf8d379e83cf6c3ab0663fba828edab344dc5f40000008e2a532cfdfdce86b27b26f611f1df0e2b00a54d18c6980e69ad28a4b70bf480336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71b4c4e01065cd22a0d2f27a544ff8315b9e28f6270a63f543831778f32648ac3a15ecbea44f91b9b4a28ec8308a30a4b6252f554a2088bb1c73ea01013ba4963dfeebabe6b0418ec13b30aadf129f5dcdd4f70ceaa56499df0b3ba9d8fe6ca410132a29cf387371294137003adb139ce9f06b6f7dbf32ee01da91016a485a8aab5669168efc3d3a200b2ee0f25d937a413dfc38e44e211f02432a01a83088e700808c302002139aad8496010300cd220b14288429d8a6049945000a3b60f00f00109019341a0129a08906314848650817002cb98894a328084521a87e6d0a64e8080004ac0805000800e29808294016508a2004001304002a02320c4705501ac008a62848701040012a08e5c8b69010f1009f80408894857053e1b940e2c9004912860e26112080610d002098242000ca001420209a42c141d0c86087509344ced8a32e0204410a0196025a12106104e20008ad4449d5043208090008810201820209108607a0fa1004aa323410050222867024e0d172a82821b02c00020513152006484c800b10f0420802c0244d0850288c6541dc34cabc32f5d355aef4017425cab95c1de0474d483623e23c9bf13d9a18f1597e930b010000000080c3c901000000000ca88800000000004b6094640000000038020000ab9bf66d04000000000000000000000000000000000000000000000000000000797be971185ac699fa7288c87457c3611b443fb98483fddf282dfa7c5b4b27058eddbcd8b5de9b747c57c827ceba820d19e987ff6740eed38ccbc947696d71e3e2c3b96c9200d7d5c66fb4e8458067d6420e64647682ca2f0798ae817d2bc19468747470733a2f2f6574682d6275696c6465722e636f6d"
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,395 +0,0 @@
package beacon
import (
"encoding/binary"
"errors"
"github.com/protolambda/zrnt/eth2/beacon/altair"
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
const MaxRequestLightClientUpdates = 128
var (
Bellatrix common.ForkDigest = [4]byte{0x0, 0x0, 0x0, 0x0}
Capella common.ForkDigest = [4]byte{0xbb, 0xa4, 0xda, 0x96}
Deneb common.ForkDigest = [4]byte{0x6a, 0x95, 0xa1, 0xa9}
)
// note: We changed the generated file since fastssz issues which can't be passed by the CI, so we commented the go:generate line
///go:generate sszgen --path types.go --exclude-objs ForkedLightClientBootstrap,ForkedLightClientUpdate,LightClientUpdateRange,ForkedLightClientOptimisticUpdate,ForkedLightClientFinalityUpdate,HistoricalSummariesProof,HistoricalSummariesWithProof,ForkedHistoricalSummariesWithProof
type LightClientUpdateKey struct {
StartPeriod uint64
Count uint64
}
type LightClientBootstrapKey struct {
BlockHash []byte `ssz-size:"32"`
}
type LightClientFinalityUpdateKey struct {
FinalizedSlot uint64
}
type LightClientOptimisticUpdateKey struct {
OptimisticSlot uint64
}
type HistoricalSummariesWithProofKey struct {
Epoch uint64
}
func (v HistoricalSummariesWithProofKey) ByteLength() uint64 {
return 8
}
func (v HistoricalSummariesWithProofKey) FixedLength() uint64 {
return 8
}
func (v HistoricalSummariesWithProofKey) Serialize(w *codec.EncodingWriter) error {
return w.WriteUint64(v.Epoch)
}
func (v *HistoricalSummariesWithProofKey) Deserialize(r *codec.DecodingReader) error {
d, err := r.ReadUint64()
if err != nil {
return err
}
v.Epoch = d
return nil
}
func (v HistoricalSummariesWithProofKey) HashTreeRoot(h tree.HashFn) common.Root {
newRoot := common.Root{}
binary.LittleEndian.PutUint64(newRoot[:], v.Epoch)
return newRoot
}
type ForkedLightClientBootstrap struct {
ForkDigest common.ForkDigest
Bootstrap common.SpecObj
}
func (flcb *ForkedLightClientBootstrap) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
_, err := dr.Read(flcb.ForkDigest[:])
if err != nil {
return err
}
if flcb.ForkDigest == Bellatrix {
flcb.Bootstrap = &altair.LightClientBootstrap{}
} else if flcb.ForkDigest == Capella {
flcb.Bootstrap = &capella.LightClientBootstrap{}
} else if flcb.ForkDigest == Deneb {
flcb.Bootstrap = &deneb.LightClientBootstrap{}
} else {
return errors.New("unknown fork digest")
}
err = flcb.Bootstrap.Deserialize(spec, dr)
if err != nil {
return err
}
return nil
}
func (flcb *ForkedLightClientBootstrap) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
if err := w.Write(flcb.ForkDigest[:]); err != nil {
return err
}
return flcb.Bootstrap.Serialize(spec, w)
}
func (flcb *ForkedLightClientBootstrap) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (flcb *ForkedLightClientBootstrap) ByteLength(spec *common.Spec) uint64 {
return 4 + flcb.Bootstrap.ByteLength(spec)
}
func (flcb *ForkedLightClientBootstrap) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
return h.HashTreeRoot(flcb.ForkDigest, spec.Wrap(flcb.Bootstrap))
}
type ForkedLightClientUpdate struct {
ForkDigest common.ForkDigest
LightClientUpdate common.SpecObj
}
func (flcu *ForkedLightClientUpdate) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
_, err := dr.Read(flcu.ForkDigest[:])
if err != nil {
return err
}
if flcu.ForkDigest == Bellatrix {
flcu.LightClientUpdate = &altair.LightClientUpdate{}
} else if flcu.ForkDigest == Capella {
flcu.LightClientUpdate = &capella.LightClientUpdate{}
} else if flcu.ForkDigest == Deneb {
flcu.LightClientUpdate = &deneb.LightClientUpdate{}
} else {
return errors.New("unknown fork digest")
}
err = flcu.LightClientUpdate.Deserialize(spec, dr)
if err != nil {
return err
}
return nil
}
func (flcu *ForkedLightClientUpdate) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
if err := w.Write(flcu.ForkDigest[:]); err != nil {
return err
}
return flcu.LightClientUpdate.Serialize(spec, w)
}
func (flcu *ForkedLightClientUpdate) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (flcu *ForkedLightClientUpdate) ByteLength(spec *common.Spec) uint64 {
return 4 + flcu.LightClientUpdate.ByteLength(spec)
}
func (flcu *ForkedLightClientUpdate) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
return h.HashTreeRoot(flcu.ForkDigest, spec.Wrap(flcu.LightClientUpdate))
}
type LightClientUpdateRange []ForkedLightClientUpdate
func (r *LightClientUpdateRange) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
return dr.List(func() codec.Deserializable {
i := len(*r)
*r = append(*r, ForkedLightClientUpdate{})
return spec.Wrap(&((*r)[i]))
}, 0, 128)
}
func (r LightClientUpdateRange) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
return w.List(func(i uint64) codec.Serializable {
return spec.Wrap(&r[i])
}, 0, uint64(len(r)))
}
func (r LightClientUpdateRange) ByteLength(spec *common.Spec) (out uint64) {
for _, v := range r {
out += v.ByteLength(spec) + codec.OFFSET_SIZE
}
return
}
func (r *LightClientUpdateRange) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (r LightClientUpdateRange) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
length := uint64(len(r))
return hFn.ComplexListHTR(func(i uint64) tree.HTR {
if i < length {
return spec.Wrap(&r[i])
}
return nil
}, length, 128)
}
type ForkedLightClientOptimisticUpdate struct {
ForkDigest common.ForkDigest
LightClientOptimisticUpdate common.SpecObj
}
func (flcou *ForkedLightClientOptimisticUpdate) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
_, err := dr.Read(flcou.ForkDigest[:])
if err != nil {
return err
}
if flcou.ForkDigest == Bellatrix {
flcou.LightClientOptimisticUpdate = &altair.LightClientOptimisticUpdate{}
} else if flcou.ForkDigest == Capella {
flcou.LightClientOptimisticUpdate = &capella.LightClientOptimisticUpdate{}
} else if flcou.ForkDigest == Deneb {
flcou.LightClientOptimisticUpdate = &deneb.LightClientOptimisticUpdate{}
} else {
return errors.New("unknown fork digest")
}
err = flcou.LightClientOptimisticUpdate.Deserialize(spec, dr)
if err != nil {
return err
}
return nil
}
func (flcou *ForkedLightClientOptimisticUpdate) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
if err := w.Write(flcou.ForkDigest[:]); err != nil {
return err
}
return flcou.LightClientOptimisticUpdate.Serialize(spec, w)
}
func (flcou *ForkedLightClientOptimisticUpdate) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (flcou *ForkedLightClientOptimisticUpdate) ByteLength(spec *common.Spec) uint64 {
return 4 + flcou.LightClientOptimisticUpdate.ByteLength(spec)
}
func (flcou *ForkedLightClientOptimisticUpdate) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
return h.HashTreeRoot(flcou.ForkDigest, spec.Wrap(flcou.LightClientOptimisticUpdate))
}
type ForkedLightClientFinalityUpdate struct {
ForkDigest common.ForkDigest
LightClientFinalityUpdate common.SpecObj
}
func (flcfu *ForkedLightClientFinalityUpdate) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
_, err := dr.Read(flcfu.ForkDigest[:])
if err != nil {
return err
}
if flcfu.ForkDigest == Bellatrix {
flcfu.LightClientFinalityUpdate = &altair.LightClientFinalityUpdate{}
} else if flcfu.ForkDigest == Capella {
flcfu.LightClientFinalityUpdate = &capella.LightClientFinalityUpdate{}
} else if flcfu.ForkDigest == Deneb {
flcfu.LightClientFinalityUpdate = &deneb.LightClientFinalityUpdate{}
} else {
return errors.New("unknown fork digest")
}
err = flcfu.LightClientFinalityUpdate.Deserialize(spec, dr)
if err != nil {
return err
}
return nil
}
func (flcfu *ForkedLightClientFinalityUpdate) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
if err := w.Write(flcfu.ForkDigest[:]); err != nil {
return err
}
return flcfu.LightClientFinalityUpdate.Serialize(spec, w)
}
func (flcfu *ForkedLightClientFinalityUpdate) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (flcfu *ForkedLightClientFinalityUpdate) ByteLength(spec *common.Spec) uint64 {
return 4 + flcfu.LightClientFinalityUpdate.ByteLength(spec)
}
func (flcfu *ForkedLightClientFinalityUpdate) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
return h.HashTreeRoot(flcfu.ForkDigest, spec.Wrap(flcfu.LightClientFinalityUpdate))
}
type HistoricalSummariesProof struct {
Proof [5]common.Bytes32
}
func (hsp *HistoricalSummariesProof) Deserialize(dr *codec.DecodingReader) error {
roots := hsp.Proof[:]
return tree.ReadRoots(dr, &roots, 5)
}
func (hsp *HistoricalSummariesProof) Serialize(w *codec.EncodingWriter) error {
return tree.WriteRoots(w, hsp.Proof[:])
}
func (hsp *HistoricalSummariesProof) ByteLength() uint64 {
return 32 * 5
}
func (hsp *HistoricalSummariesProof) FixedLength() uint64 {
return 32 * 5
}
func (hsp *HistoricalSummariesProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.ComplexVectorHTR(func(i uint64) tree.HTR {
if i < 5 {
return &hsp.Proof[i]
}
return nil
}, 5)
}
type HistoricalSummariesWithProof struct {
EPOCH common.Epoch
HistoricalSummaries capella.HistoricalSummaries
Proof HistoricalSummariesProof
}
func (hswp *HistoricalSummariesWithProof) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
return dr.Container(&hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), &hswp.Proof)
}
func (hswp *HistoricalSummariesWithProof) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
return w.Container(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), &hswp.Proof)
}
func (hswp *HistoricalSummariesWithProof) ByteLength(spec *common.Spec) uint64 {
return codec.ContainerLength(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), &hswp.Proof)
}
func (hswp *HistoricalSummariesWithProof) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (hswp *HistoricalSummariesWithProof) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), &hswp.Proof)
}
type ForkedHistoricalSummariesWithProof struct {
ForkDigest common.ForkDigest
HistoricalSummariesWithProof HistoricalSummariesWithProof
}
func (fhswp *ForkedHistoricalSummariesWithProof) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
_, err := dr.Read(fhswp.ForkDigest[:])
if err != nil {
return err
}
err = fhswp.HistoricalSummariesWithProof.Deserialize(spec, dr)
if err != nil {
return err
}
return nil
}
func (fhswp *ForkedHistoricalSummariesWithProof) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
if err := w.Write(fhswp.ForkDigest[:]); err != nil {
return err
}
return fhswp.HistoricalSummariesWithProof.Serialize(spec, w)
}
func (fhswp ForkedHistoricalSummariesWithProof) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (fhswp ForkedHistoricalSummariesWithProof) ByteLength(spec *common.Spec) uint64 {
return 4 + fhswp.HistoricalSummariesWithProof.ByteLength(spec)
}
func (fhswp ForkedHistoricalSummariesWithProof) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
return h.HashTreeRoot(fhswp.ForkDigest, spec.Wrap(common.SpecObj(&fhswp.HistoricalSummariesWithProof)))
}

View file

@ -1,252 +0,0 @@
// Code generated by fastssz. DO NOT EDIT.
// Hash: 7b06aa2a0612821c21a42cced443e791afc5c4f842d09312d0f29e40102138a3
// Version: 0.1.3
package beacon
import (
ssz "github.com/ferranbt/fastssz"
)
// MarshalSSZ ssz marshals the LightClientUpdateKey object
func (l *LightClientUpdateKey) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(l)
}
// MarshalSSZTo ssz marshals the LightClientUpdateKey object to a target array
func (l *LightClientUpdateKey) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'StartPeriod'
dst = ssz.MarshalUint64(dst, l.StartPeriod)
// Field (1) 'Count'
dst = ssz.MarshalUint64(dst, l.Count)
return
}
// UnmarshalSSZ ssz unmarshals the LightClientUpdateKey object
func (l *LightClientUpdateKey) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 16 {
return ssz.ErrSize
}
// Field (0) 'StartPeriod'
l.StartPeriod = ssz.UnmarshallUint64(buf[0:8])
// Field (1) 'Count'
l.Count = ssz.UnmarshallUint64(buf[8:16])
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the LightClientUpdateKey object
func (l *LightClientUpdateKey) SizeSSZ() (size int) {
size = 16
return
}
// HashTreeRoot ssz hashes the LightClientUpdateKey object
func (l *LightClientUpdateKey) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(l)
}
// HashTreeRootWith ssz hashes the LightClientUpdateKey object with a hasher
func (l *LightClientUpdateKey) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'StartPeriod'
hh.PutUint64(l.StartPeriod)
// Field (1) 'Count'
hh.PutUint64(l.Count)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the LightClientUpdateKey object
func (l *LightClientUpdateKey) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(l)
}
// MarshalSSZ ssz marshals the LightClientBootstrapKey object
func (l *LightClientBootstrapKey) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(l)
}
// MarshalSSZTo ssz marshals the LightClientBootstrapKey object to a target array
func (l *LightClientBootstrapKey) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'BlockHash'
if size := len(l.BlockHash); size != 32 {
err = ssz.ErrBytesLengthFn("LightClientBootstrapKey.BlockHash", size, 32)
return
}
dst = append(dst, l.BlockHash...)
return
}
// UnmarshalSSZ ssz unmarshals the LightClientBootstrapKey object
func (l *LightClientBootstrapKey) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 32 {
return ssz.ErrSize
}
// Field (0) 'BlockHash'
if cap(l.BlockHash) == 0 {
l.BlockHash = make([]byte, 0, len(buf[0:32]))
}
l.BlockHash = append(l.BlockHash, buf[0:32]...)
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the LightClientBootstrapKey object
func (l *LightClientBootstrapKey) SizeSSZ() (size int) {
size = 32
return
}
// HashTreeRoot ssz hashes the LightClientBootstrapKey object
func (l *LightClientBootstrapKey) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(l)
}
// HashTreeRootWith ssz hashes the LightClientBootstrapKey object with a hasher
func (l *LightClientBootstrapKey) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'BlockHash'
if size := len(l.BlockHash); size != 32 {
err = ssz.ErrBytesLengthFn("LightClientBootstrapKey.BlockHash", size, 32)
return
}
hh.PutBytes(l.BlockHash)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the LightClientBootstrapKey object
func (l *LightClientBootstrapKey) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(l)
}
// MarshalSSZ ssz marshals the LightClientFinalityUpdateKey object
func (l *LightClientFinalityUpdateKey) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(l)
}
// MarshalSSZTo ssz marshals the LightClientFinalityUpdateKey object to a target array
func (l *LightClientFinalityUpdateKey) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'FinalizedSlot'
dst = ssz.MarshalUint64(dst, l.FinalizedSlot)
return
}
// UnmarshalSSZ ssz unmarshals the LightClientFinalityUpdateKey object
func (l *LightClientFinalityUpdateKey) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 8 {
return ssz.ErrSize
}
// Field (0) 'FinalizedSlot'
l.FinalizedSlot = ssz.UnmarshallUint64(buf[0:8])
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the LightClientFinalityUpdateKey object
func (l *LightClientFinalityUpdateKey) SizeSSZ() (size int) {
size = 8
return
}
// HashTreeRoot ssz hashes the LightClientFinalityUpdateKey object
func (l *LightClientFinalityUpdateKey) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(l)
}
// HashTreeRootWith ssz hashes the LightClientFinalityUpdateKey object with a hasher
func (l *LightClientFinalityUpdateKey) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'FinalizedSlot'
hh.PutUint64(l.FinalizedSlot)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the LightClientFinalityUpdateKey object
func (l *LightClientFinalityUpdateKey) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(l)
}
// MarshalSSZ ssz marshals the LightClientOptimisticUpdateKey object
func (l *LightClientOptimisticUpdateKey) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(l)
}
// MarshalSSZTo ssz marshals the LightClientOptimisticUpdateKey object to a target array
func (l *LightClientOptimisticUpdateKey) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'OptimisticSlot'
dst = ssz.MarshalUint64(dst, l.OptimisticSlot)
return
}
// UnmarshalSSZ ssz unmarshals the LightClientOptimisticUpdateKey object
func (l *LightClientOptimisticUpdateKey) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 8 {
return ssz.ErrSize
}
// Field (0) 'OptimisticSlot'
l.OptimisticSlot = ssz.UnmarshallUint64(buf[0:8])
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the LightClientOptimisticUpdateKey object
func (l *LightClientOptimisticUpdateKey) SizeSSZ() (size int) {
size = 8
return
}
// HashTreeRoot ssz hashes the LightClientOptimisticUpdateKey object
func (l *LightClientOptimisticUpdateKey) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(l)
}
// HashTreeRootWith ssz hashes the LightClientOptimisticUpdateKey object with a hasher
func (l *LightClientOptimisticUpdateKey) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'OptimisticSlot'
hh.PutUint64(l.OptimisticSlot)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the LightClientOptimisticUpdateKey object
func (l *LightClientOptimisticUpdateKey) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(l)
}

View file

@ -1,153 +0,0 @@
package beacon
import (
"bytes"
"encoding/json"
"io"
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/codec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestForkedLightClientBootstrap(t *testing.T) {
filePath := "testdata/types/light_client_bootstrap.json"
f, err := os.Open(filePath)
require.NoError(t, err)
jsonStr, err := io.ReadAll(f)
require.NoError(t, err)
var result map[string]interface{}
_ = json.Unmarshal(jsonStr, &result)
for k, v := range result {
b, _ := hexutil.Decode(v.(map[string]interface{})["content_value"].(string))
dec := codec.NewDecodingReader(bytes.NewReader(b), uint64(len(b)))
var f ForkedLightClientBootstrap
err := f.Deserialize(configs.Mainnet, dec)
assert.NoError(t, err)
assert.Equal(t, k, f.Bootstrap.(*capella.LightClientBootstrap).Header.Beacon.Slot.String())
var buf bytes.Buffer
err = f.Serialize(configs.Mainnet, codec.NewEncodingWriter(&buf))
assert.NoError(t, err)
assert.Equal(t, b, buf.Bytes())
}
}
func TestLightClientUpdateRange(t *testing.T) {
filePath := "testdata/types/light_client_updates_by_range.json"
f, err := os.Open(filePath)
require.NoError(t, err)
jsonStr, err := io.ReadAll(f)
require.NoError(t, err)
var result map[string]interface{}
_ = json.Unmarshal(jsonStr, &result)
for k, v := range result {
b, _ := hexutil.Decode(v.(map[string]interface{})["content_value"].(string))
dec := codec.NewDecodingReader(bytes.NewReader(b), uint64(len(b)))
var f LightClientUpdateRange = make([]ForkedLightClientUpdate, 0)
err := f.Deserialize(configs.Mainnet, dec)
assert.NoError(t, err)
assert.Equal(t, k, f[0].LightClientUpdate.(*capella.LightClientUpdate).AttestedHeader.Beacon.Slot.String())
assert.Equal(t, 4, len(f))
var buf bytes.Buffer
err = f.Serialize(configs.Mainnet, codec.NewEncodingWriter(&buf))
assert.NoError(t, err)
assert.Equal(t, b, buf.Bytes())
}
}
func TestForkedLightClientOptimisticUpdate(t *testing.T) {
filePath := "testdata/types/light_client_optimistic_update.json"
f, err := os.Open(filePath)
require.NoError(t, err)
jsonStr, err := io.ReadAll(f)
require.NoError(t, err)
var result map[string]interface{}
_ = json.Unmarshal(jsonStr, &result)
for k, v := range result {
b, _ := hexutil.Decode(v.(map[string]interface{})["content_value"].(string))
dec := codec.NewDecodingReader(bytes.NewReader(b), uint64(len(b)))
var f ForkedLightClientOptimisticUpdate
err := f.Deserialize(configs.Mainnet, dec)
assert.NoError(t, err)
assert.Equal(t, k, f.LightClientOptimisticUpdate.(*capella.LightClientOptimisticUpdate).AttestedHeader.Beacon.Slot.String())
var buf bytes.Buffer
err = f.Serialize(configs.Mainnet, codec.NewEncodingWriter(&buf))
assert.NoError(t, err)
assert.Equal(t, b, buf.Bytes())
}
}
func TestForkedLightClientFinalityUpdate(t *testing.T) {
filePath := "testdata/types/light_client_finality_update.json"
f, err := os.Open(filePath)
require.NoError(t, err)
jsonStr, err := io.ReadAll(f)
require.NoError(t, err)
var result map[string]interface{}
_ = json.Unmarshal(jsonStr, &result)
for k, v := range result {
b, _ := hexutil.Decode(v.(map[string]interface{})["content_value"].(string))
dec := codec.NewDecodingReader(bytes.NewReader(b), uint64(len(b)))
var f ForkedLightClientFinalityUpdate
err := f.Deserialize(configs.Mainnet, dec)
assert.NoError(t, err)
assert.Equal(t, k, f.LightClientFinalityUpdate.(*capella.LightClientFinalityUpdate).AttestedHeader.Beacon.Slot.String())
var buf bytes.Buffer
err = f.Serialize(configs.Mainnet, codec.NewEncodingWriter(&buf))
assert.NoError(t, err)
assert.Equal(t, b, buf.Bytes())
}
}
type TestProof struct {
ContentKey string `yaml:"content_key"`
ContentValue string `yaml:"content_value"`
BeaconStateRoot string `yaml:"beacon_state_root"`
HistoricalSummariesRoot string `yaml:"historical_summaries_root"`
HistoricalSummariesStateProof []string `yaml:"historical_summaries_state_proof"`
Epoch uint64 `yaml:"epoch"`
}
func TestForkedHistoricalSummariesWithProof(t *testing.T) {
filePath := "testdata/types/historical_summaries_with_proof.yaml"
f, err := os.Open(filePath)
require.NoError(t, err)
contentBytes, err := io.ReadAll(f)
require.NoError(t, err)
testData := TestProof{}
err = yaml.Unmarshal(contentBytes, &testData)
require.NoError(t, err)
historyKey := &HistoricalSummariesWithProofKey{}
contentKey := hexutil.MustDecode(testData.ContentKey)
err = historyKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey[1:]), uint64(len(contentKey)-1)))
require.NoError(t, err)
require.Equal(t, testData.Epoch, historyKey.Epoch)
historyProof := &ForkedHistoricalSummariesWithProof{}
content := hexutil.MustDecode(testData.ContentValue)
err = historyProof.Deserialize(configs.Mainnet, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
require.NoError(t, err)
require.Equal(t, uint64(historyProof.HistoricalSummariesWithProof.EPOCH), testData.Epoch)
}

View file

@ -1,138 +0,0 @@
package ethapi
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/portalnetwork/history"
"github.com/ethereum/go-ethereum/rpc"
)
var errParameterNotImplemented = errors.New("parameter not implemented")
// marshalReceipt marshals a transaction receipt into a JSON object.
func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
from, _ := types.Sender(signer, tx)
fields := map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(blockNumber),
"transactionHash": tx.Hash(),
"transactionIndex": hexutil.Uint64(txIndex),
"from": from,
"to": tx.To(),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()),
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
}
// Assign receipt status or post state.
if len(receipt.PostState) > 0 {
fields["root"] = hexutil.Bytes(receipt.PostState)
} else {
fields["status"] = hexutil.Uint(receipt.Status)
}
if receipt.Logs == nil {
fields["logs"] = []*types.Log{}
}
if tx.Type() == types.BlobTxType {
fields["blobGasUsed"] = hexutil.Uint64(receipt.BlobGasUsed)
fields["blobGasPrice"] = (*hexutil.Big)(receipt.BlobGasPrice)
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}
return fields
}
type API struct {
History *history.Network
ChainID *big.Int
}
func (p *API) ChainId() hexutil.Uint64 {
return (hexutil.Uint64)(p.ChainID.Uint64())
}
func (p *API) GetBlockByHash(hash *common.Hash, fullTransactions bool) (map[string]interface{}, error) {
blockHeader, err := p.History.GetBlockHeader(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil, err
}
blockBody, err := p.History.GetBlockBody(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil, err
}
block := types.NewBlockWithHeader(blockHeader).WithBody(*blockBody)
//static configuration of Config, currently only mainnet implemented
return ethapi.RPCMarshalBlock(block, true, fullTransactions, params.MainnetChainConfig), nil
}
func (p *API) GetBlockReceipts(blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
hash, isHhash := blockNrOrHash.Hash()
if !isHhash {
return nil, errParameterNotImplemented
}
blockReceipts, err := p.History.GetReceipts(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil, err
}
blockBody, err := p.History.GetBlockBody(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil, err
}
blockHeader, err := p.History.GetBlockHeader(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil, err
}
txs := blockBody.Transactions
if len(txs) != len(blockReceipts) {
return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(blockReceipts))
}
// Derive the sender.
signer := types.MakeSigner(params.MainnetChainConfig, blockHeader.Number, blockHeader.Time)
result := make([]map[string]interface{}, len(blockReceipts))
for i, receipt := range blockReceipts {
result[i] = marshalReceipt(receipt, blockHeader.Hash(), blockHeader.Number.Uint64(), signer, txs[i], i)
}
return result, nil
}
func (p *API) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
blockBody, err := p.History.GetBlockBody(hash.Bytes())
if err != nil {
log.Error(err.Error())
return nil
}
n := hexutil.Uint(len(blockBody.Transactions))
return &n
}

View file

@ -1,300 +0,0 @@
package history
import (
"bytes"
_ "embed"
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
ssz "github.com/ferranbt/fastssz"
"github.com/holiman/uint256"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/util/merkle"
"github.com/protolambda/ztyp/codec"
)
const (
epochSize = 8192
mergeBlockNumber uint64 = 15537394
shanghaiBlockNumber uint64 = 17_034_870
preMergeEpochs = (mergeBlockNumber + epochSize - 1) / epochSize
)
var (
ErrNotPreMergeHeader = errors.New("must be pre merge header")
ErrPreMergeHeaderMustWithProof = errors.New("pre merge header must has accumulator proof")
)
//go:embed assets/merge_macc.txt
var masterAccumulatorHex string
//go:embed assets/historical_roots.ssz
var historicalRootsBytes []byte
var zeroRecordBytes = make([]byte, 64)
type AccumulatorProof [][]byte
type epoch struct {
records [][]byte
difficulty *uint256.Int
}
func newEpoch() *epoch {
return &epoch{
records: make([][]byte, 0, epochSize),
difficulty: uint256.NewInt(0),
}
}
func (e *epoch) add(header types.Header) error {
blockHash := header.Hash().Bytes()
difficulty := uint256.MustFromBig(header.Difficulty)
e.difficulty = uint256.NewInt(0).Add(e.difficulty, difficulty)
difficultyBytes, err := e.difficulty.MarshalSSZ()
if err != nil {
return err
}
record := HeaderRecord{
BlockHash: blockHash,
TotalDifficulty: difficultyBytes,
}
sszBytes, err := record.MarshalSSZ()
if err != nil {
return err
}
e.records = append(e.records, sszBytes)
return nil
}
type Accumulator struct {
historicalEpochs [][]byte
currentEpoch *epoch
}
type BlockEpochData struct {
epochHash []byte
blockRelativeIndex uint64
}
func NewAccumulator() *Accumulator {
return &Accumulator{
historicalEpochs: make([][]byte, 0, int(preMergeEpochs)),
currentEpoch: newEpoch(),
}
}
func (a *Accumulator) Update(header types.Header) error {
if header.Number.Uint64() >= mergeBlockNumber {
return ErrNotPreMergeHeader
}
if len(a.currentEpoch.records) == epochSize {
epochAccu := EpochAccumulator{
HeaderRecords: a.currentEpoch.records,
}
root, err := epochAccu.HashTreeRoot()
if err != nil {
return err
}
a.historicalEpochs = append(a.historicalEpochs, MixInLength(root, epochSize))
a.currentEpoch = newEpoch()
}
err := a.currentEpoch.add(header)
if err != nil {
return err
}
return nil
}
func (a *Accumulator) Finish() (*MasterAccumulator, error) {
// padding with zero bytes
for len(a.currentEpoch.records) < epochSize {
a.currentEpoch.records = append(a.currentEpoch.records, zeroRecordBytes)
}
epochAccu := EpochAccumulator{
HeaderRecords: a.currentEpoch.records,
}
root, err := epochAccu.HashTreeRoot()
if err != nil {
return nil, err
}
a.historicalEpochs = append(a.historicalEpochs, MixInLength(root, epochSize))
return &MasterAccumulator{
HistoricalEpochs: a.historicalEpochs,
}, nil
}
func GetEpochIndex(blockNumber uint64) uint64 {
return blockNumber / epochSize
}
func GetEpochIndexByHeader(header types.Header) uint64 {
return GetEpochIndex(header.Number.Uint64())
}
func GetHeaderRecordIndex(blockNumber uint64) uint64 {
return blockNumber % epochSize
}
func GetHeaderRecordIndexByHeader(header types.Header) uint64 {
return GetHeaderRecordIndex(header.Number.Uint64())
}
func BuildProof(header types.Header, epochAccumulator EpochAccumulator) (AccumulatorProof, error) {
tree, err := epochAccumulator.GetTree()
if err != nil {
return nil, err
}
index := GetHeaderRecordIndexByHeader(header)
// maybe the calculation of index should impl in ssz
proofIndex := epochSize*2 + index*2
sszProof, err := tree.Prove(int(proofIndex))
if err != nil {
return nil, err
}
// the epoch hash root has mix in with epochsize, so we have to add it to proof
hashes := sszProof.Hashes
sizeBytes := make([]byte, 32)
binary.LittleEndian.PutUint32(sizeBytes, epochSize)
hashes = append(hashes, sizeBytes)
return hashes, err
}
func BuildHeaderWithProof(header types.Header, epochAccumulator EpochAccumulator) (*BlockHeaderWithProof, error) {
proof, err := BuildProof(header, epochAccumulator)
if err != nil {
return nil, err
}
rlpBytes, err := rlp.EncodeToBytes(header)
if err != nil {
return nil, err
}
return &BlockHeaderWithProof{
Header: rlpBytes,
Proof: &BlockHeaderProof{
Selector: accumulatorProof,
Proof: proof,
},
}, nil
}
func (f MasterAccumulator) GetBlockEpochDataForBlockNumber(blockNumber uint64) BlockEpochData {
epochIndex := GetEpochIndex(blockNumber)
return BlockEpochData{
epochHash: f.HistoricalEpochs[epochIndex],
blockRelativeIndex: GetHeaderRecordIndex(blockNumber),
}
}
func (f MasterAccumulator) VerifyAccumulatorProof(header types.Header, proof AccumulatorProof) (bool, error) {
if header.Number.Uint64() > mergeBlockNumber {
return false, ErrNotPreMergeHeader
}
epochIndex := GetEpochIndexByHeader(header)
root := f.HistoricalEpochs[epochIndex]
valid := verifyProof(root, header, proof)
return valid, nil
}
func (f MasterAccumulator) VerifyHeader(header types.Header, headerProof BlockHeaderProof) (bool, error) {
switch headerProof.Selector {
case accumulatorProof:
return f.VerifyAccumulatorProof(header, headerProof.Proof)
case none:
if header.Number.Uint64() <= mergeBlockNumber {
return false, ErrPreMergeHeaderMustWithProof
}
return true, nil
}
return false, fmt.Errorf("unknown header proof selector %v", headerProof.Selector)
}
func (f MasterAccumulator) Contains(epochHash []byte) bool {
for _, h := range f.HistoricalEpochs {
if bytes.Equal(h, epochHash) {
return true
}
}
return false
}
func MixInLength(root [32]byte, length uint64) []byte {
hash := ssz.NewHasher()
hash.AppendBytes32(root[:])
hash.MerkleizeWithMixin(0, length, 0)
// length of root is 32, so we can ignore the error
newRoot, _ := hash.HashRoot()
return newRoot[:]
}
func verifyProof(root []byte, header types.Header, proof AccumulatorProof) bool {
leaf := header.Hash()
recordIndex := GetHeaderRecordIndexByHeader(header)
index := epochSize*2*2 + recordIndex*2
sszProof := &ssz.Proof{
Index: int(index),
Leaf: leaf[:],
Hashes: proof,
}
valid, err := ssz.VerifyProof(root, sszProof)
if err != nil {
return false
}
return valid
}
func NewMasterAccumulator() (MasterAccumulator, error) {
var masterAcc = MasterAccumulator{
HistoricalEpochs: make([][]byte, 0),
}
masterAccumulatorBytes, err := hexutil.Decode(masterAccumulatorHex)
if err != nil {
return masterAcc, err
}
err = masterAcc.UnmarshalSSZ(masterAccumulatorBytes)
return masterAcc, err
}
type HistoricalRootsAccumulator struct {
HistoricalRoots HistoricalRoots
}
func NewHistoricalRootsAccumulator(spec *common.Spec) (HistoricalRootsAccumulator, error) {
historicalRoots := new(HistoricalRoots)
reader := codec.NewDecodingReader(bytes.NewReader(historicalRootsBytes), uint64(len(historicalRootsBytes)))
err := historicalRoots.Deserialize(spec, reader)
return HistoricalRootsAccumulator{HistoricalRoots: *historicalRoots}, err
}
func (h HistoricalRootsAccumulator) VerifyPostMergePreCapellaHeader(blockNumber uint64, headerHash common.Root, proof *HistoricalRootsBlockProof) error {
if blockNumber <= mergeBlockNumber {
return errors.New("invalid historicalRootsBlockProof found for pre-merge header")
}
if blockNumber >= shanghaiBlockNumber {
return errors.New("invalid historicalRootsBlockProof found for post-Shanghai header")
}
if !merkle.VerifyMerkleBranch(headerHash, proof.BeaconBlockBodyProof[:], 8, 412, proof.BeaconBlockBodyRoot) {
return errors.New("merkle proof validation failed for BeaconBlockBodyProof")
}
if !merkle.VerifyMerkleBranch(proof.BeaconBlockBodyRoot, proof.BeaconBlockHeaderProof[:], 3, 12, proof.BeaconBlockHeaderRoot) {
return errors.New("merkle proof validation failed for BeaconBlockHeaderProof")
}
blockRootIndex := proof.Slot % epochSize
genIndex := 2*epochSize + blockRootIndex
historicalRootIndex := proof.Slot / epochSize
historicalRoot := h.HistoricalRoots[historicalRootIndex]
if !merkle.VerifyMerkleBranch(proof.BeaconBlockHeaderRoot, proof.HistoricalRootsProof[:], 14, uint64(genIndex), historicalRoot) {
return errors.New("merkle proof validation failed for HistoricalRootsProof")
}
return nil
}

View file

@ -1,171 +0,0 @@
package history
import (
"bytes"
"encoding/json"
"fmt"
"os"
"strconv"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/tree"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestVerifyHeaderWithProofs(t *testing.T) {
headerWithProofs, err := parseHeaderWithProof()
assert.NoError(t, err)
masterAcc, err := NewMasterAccumulator()
assert.NoError(t, err)
for _, val := range headerWithProofs {
head := types.Header{}
err := rlp.DecodeBytes(val.Header, &head)
assert.NoError(t, err)
valid, err := masterAcc.VerifyHeader(head, *val.Proof)
assert.NoError(t, err)
assert.True(t, valid)
}
}
func TestBuildAndVerifyProof(t *testing.T) {
masterAcc, err := NewMasterAccumulator()
assert.NoError(t, err)
epochIndex := GetEpochIndex(1000003)
epochStr := hexutil.Encode(masterAcc.HistoricalEpochs[epochIndex])
epochAccumulator, err := getEpochAccu(epochStr)
assert.NoError(t, err)
for i := 1000001; i < 1000011; i++ {
header, err := getHeader(1000003)
assert.NoError(t, err)
proof, err := BuildProof(*header, epochAccumulator)
assert.NoError(t, err)
valid, err := masterAcc.VerifyAccumulatorProof(*header, proof)
assert.NoError(t, err)
assert.True(t, valid)
assert.True(t, valid)
}
}
func TestUpdate(t *testing.T) {
epochAcc, err := getEpochAccu("0xcddbda3fd6f764602c06803ff083dbfc73f2bb396df17a31e5457329b9a0f38d")
assert.NoError(t, err)
startNumber := 1000000
epochRecordIndex := GetHeaderRecordIndex(uint64(startNumber))
newEpochAcc := NewAccumulator()
for i := 0; i <= int(epochRecordIndex); i++ {
tmp := make([]byte, 64)
copy(tmp, epochAcc.HeaderRecords[i])
newEpochAcc.currentEpoch.records = append(newEpochAcc.currentEpoch.records, tmp)
}
startDifficulty := uint256.NewInt(0)
err = startDifficulty.UnmarshalSSZ(epochAcc.HeaderRecords[epochRecordIndex][32:])
require.NoError(t, err)
newEpochAcc.currentEpoch.difficulty = startDifficulty
for i := startNumber + 1; i <= 1000010; i++ {
header, err := getHeader(uint64(i))
assert.NoError(t, err)
err = newEpochAcc.Update(*header)
assert.NoError(t, err)
currIndex := GetHeaderRecordIndex(uint64(i))
assert.True(t, bytes.Equal(newEpochAcc.currentEpoch.records[currIndex], epochAcc.HeaderRecords[currIndex]))
}
}
func TestVerifyPostMergePreCapellaHeader(t *testing.T) {
acc, err := NewHistoricalRootsAccumulator(configs.Mainnet)
require.NoError(t, err)
require.True(t, uint64(len(acc.HistoricalRoots)) < uint64(configs.Mainnet.HISTORICAL_ROOTS_LIMIT))
root := acc.HistoricalRoots.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
hexutil.Encode(root[:])
require.Equal(t, hexutil.Encode(root[:]), "0x4df6b89755125d4f6c5575039a04e22301a5a49ee893c1d27e559e3eeab73da7")
file, err := os.ReadFile("./testdata/block_proofs_bellatrix/beacon_block_proof-15539558-cdf9ed89b0c43cda17398dc4da9cfc505e5ccd19f7c39e3b43474180f1051e01.yaml")
require.NoError(t, err)
proof := HistoricalRootsBlockProof{}
err = yaml.Unmarshal(file, &proof)
require.NoError(t, err)
// blockNumber and blockHash are from testfile
blockHash := hexutil.MustDecode("0xcdf9ed89b0c43cda17398dc4da9cfc505e5ccd19f7c39e3b43474180f1051e01")
err = acc.VerifyPostMergePreCapellaHeader(15539558, tree.Root(blockHash), &proof)
require.NoError(t, err)
}
// all test blocks are in the same epoch
func parseHeaderWithProof() ([]BlockHeaderWithProof, error) {
headWithProofBytes, err := os.ReadFile("./testdata/header_with_proofs.json")
if err != nil {
return nil, err
}
headerMap := make(map[string]map[string]string)
err = json.Unmarshal(headWithProofBytes, &headerMap)
if err != nil {
return nil, err
}
res := make([]BlockHeaderWithProof, 0)
for _, v := range headerMap {
val := v["value"]
bytes, err := hexutil.Decode(val)
if err != nil {
return nil, err
}
headWithProof := BlockHeaderWithProof{}
err = headWithProof.UnmarshalSSZ(bytes)
if err != nil {
return nil, err
}
res = append(res, headWithProof)
}
return res, nil
}
func getEpochAccu(name string) (EpochAccumulator, error) {
epochAccu := EpochAccumulator{
HeaderRecords: make([][]byte, 0),
}
epochData, err := os.ReadFile(fmt.Sprintf("./testdata/%s.bin", name))
if err != nil {
return epochAccu, err
}
err = epochAccu.UnmarshalSSZ(epochData)
return epochAccu, err
}
func getHeader(number uint64) (*types.Header, error) {
headerFile, err := os.ReadFile("./testdata/header_rlps.json")
if err != nil {
return nil, err
}
contentMap := make(map[string]string)
err = json.Unmarshal(headerFile, &contentMap)
if err != nil {
return nil, err
}
headerStr := contentMap[strconv.FormatUint(number, 10)]
headerBytes, err := hexutil.Decode(headerStr)
if err != nil {
return nil, err
}
reader := bytes.NewReader(headerBytes)
head := &types.Header{}
err = rlp.Decode(reader, head)
return head, err
}

View file

@ -1,75 +0,0 @@
package history
import (
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
)
type API struct {
*portalwire.PortalProtocolAPI
}
func (p *API) HistoryRoutingTableInfo() *portalwire.RoutingTableInfo {
return p.RoutingTableInfo()
}
func (p *API) HistoryAddEnr(enr string) (bool, error) {
return p.AddEnr(enr)
}
func (p *API) HistoryGetEnr(nodeId string) (string, error) {
return p.GetEnr(nodeId)
}
func (p *API) HistoryDeleteEnr(nodeId string) (bool, error) {
return p.DeleteEnr(nodeId)
}
func (p *API) HistoryLookupEnr(nodeId string) (string, error) {
return p.LookupEnr(nodeId)
}
func (p *API) HistoryPing(enr string) (*portalwire.PortalPongResp, error) {
return p.Ping(enr)
}
func (p *API) HistoryFindNodes(enr string, distances []uint) ([]string, error) {
return p.FindNodes(enr, distances)
}
func (p *API) HistoryFindContent(enr string, contentKey string) (interface{}, error) {
return p.FindContent(enr, contentKey)
}
func (p *API) HistoryOffer(enr string, contentItems [][2]string) (string, error) {
return p.Offer(enr, contentItems)
}
func (p *API) HistoryRecursiveFindNodes(nodeId string) ([]string, error) {
return p.RecursiveFindNodes(nodeId)
}
func (p *API) HistoryGetContent(contentKeyHex string) (*portalwire.ContentInfo, error) {
return p.RecursiveFindContent(contentKeyHex)
}
func (p *API) HistoryLocalContent(contentKeyHex string) (string, error) {
return p.LocalContent(contentKeyHex)
}
func (p *API) HistoryStore(contentKeyHex string, contextHex string) (bool, error) {
return p.Store(contentKeyHex, contextHex)
}
func (p *API) HistoryGossip(contentKeyHex, contentHex string) (int, error) {
return p.Gossip(contentKeyHex, contentHex)
}
func (p *API) HistoryTraceGetContent(contentKeyHex string) (*portalwire.TraceContentResult, error) {
return p.TraceRecursiveFindContent(contentKeyHex)
}
func NewHistoryNetworkAPI(historyAPI *portalwire.PortalProtocolAPI) *API {
return &API{
historyAPI,
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,608 +0,0 @@
package history
import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/view"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
type ContentType byte
const (
BlockHeaderType ContentType = 0x00
BlockBodyType ContentType = 0x01
ReceiptsType ContentType = 0x02
BlockHeaderNumberType ContentType = 0x03
// EpochAccumulatorType ContentType = 0x03
)
var (
ErrWithdrawalHashIsNotEqual = errors.New("withdrawals hash is not equal")
ErrTxHashIsNotEqual = errors.New("tx hash is not equal")
ErrUnclesHashIsNotEqual = errors.New("uncles hash is not equal")
ErrReceiptsHashIsNotEqual = errors.New("receipts hash is not equal")
ErrContentOutOfRange = errors.New("content out of range")
ErrHeaderWithProofIsInvalid = errors.New("header proof is invalid")
ErrInvalidBlockHash = errors.New("invalid block hash")
ErrInvalidBlockNumber = errors.New("invalid block number")
)
var emptyReceiptHash = hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
type ContentKey struct {
selector ContentType
data []byte
}
func newContentKey(selector ContentType, hash []byte) *ContentKey {
return &ContentKey{
selector: selector,
data: hash,
}
}
func (c *ContentKey) encode() []byte {
res := make([]byte, 0, len(c.data)+1)
res = append(res, byte(c.selector))
res = append(res, c.data...)
return res
}
type Network struct {
portalProtocol *portalwire.PortalProtocol
masterAccumulator *MasterAccumulator
closeCtx context.Context
closeFunc context.CancelFunc
log log.Logger
}
func NewHistoryNetwork(portalProtocol *portalwire.PortalProtocol, accu *MasterAccumulator) *Network {
ctx, cancel := context.WithCancel(context.Background())
return &Network{
portalProtocol: portalProtocol,
masterAccumulator: accu,
closeCtx: ctx,
closeFunc: cancel,
log: log.New("sub-protocol", "history"),
}
}
func (h *Network) Start() error {
err := h.portalProtocol.Start()
if err != nil {
return err
}
go h.processContentLoop(h.closeCtx)
h.log.Debug("history network start successfully")
return nil
}
func (h *Network) Stop() {
h.closeFunc()
h.portalProtocol.Stop()
}
// Currently doing 4 retries on lookups but only when the validation fails.
const requestRetries = 4
func (h *Network) GetBlockHeader(blockHash []byte) (*types.Header, error) {
contentKey := newContentKey(BlockHeaderType, blockHash).encode()
contentId := h.portalProtocol.ToContentId(contentKey)
h.log.Trace("contentKey convert to contentId", "contentKey", hexutil.Encode(contentKey), "contentId", hexutil.Encode(contentId))
if !h.portalProtocol.InRange(contentId) {
return nil, ErrContentOutOfRange
}
res, err := h.portalProtocol.Get(contentKey, contentId)
// other error
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
return nil, err
}
// no error
if err == nil {
blockHeaderWithProof, err := DecodeBlockHeaderWithProof(res)
if err != nil {
return nil, err
}
header := new(types.Header)
err = rlp.DecodeBytes(blockHeaderWithProof.Header, header)
return header, err
}
// no content in local storage
for retries := 0; retries < requestRetries; retries++ {
content, _, err := h.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
h.log.Error("getBlockHeader failed", "contentKey", hexutil.Encode(contentKey), "err", err)
continue
}
headerWithProof, err := DecodeBlockHeaderWithProof(content)
if err != nil {
h.log.Error("decodeBlockHeaderWithProof failed", "content", hexutil.Encode(content), "err", err)
continue
}
header, err := ValidateBlockHeaderBytes(headerWithProof.Header, blockHash)
if err != nil {
h.log.Error("validateBlockHeaderBytes failed", "header", hexutil.Encode(headerWithProof.Header), "blockhash", hexutil.Encode(blockHash), "err", err)
continue
}
valid, err := h.verifyHeader(header, *headerWithProof.Proof)
if err != nil || !valid {
h.log.Error("verifyHeader failed", "err", err)
continue
}
err = h.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
h.log.Error("failed to store content in getBlockHeader", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content))
}
return header, nil
}
return nil, storage.ErrContentNotFound
}
func (h *Network) GetBlockBody(blockHash []byte) (*types.Body, error) {
header, err := h.GetBlockHeader(blockHash)
if err != nil {
return nil, err
}
contentKey := newContentKey(BlockBodyType, blockHash).encode()
contentId := h.portalProtocol.ToContentId(contentKey)
if !h.portalProtocol.InRange(contentId) {
return nil, ErrContentOutOfRange
}
res, err := h.portalProtocol.Get(contentKey, contentId)
// other error
// TODO maybe use nil res to replace the ErrContentNotFound
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
return nil, err
}
// no error
if err == nil {
body, err := DecodePortalBlockBodyBytes(res)
return body, err
}
// no content in local storage
for retries := 0; retries < requestRetries; retries++ {
content, _, err := h.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
h.log.Error("getBlockBody failed", "contentKey", hexutil.Encode(contentKey), "err", err)
continue
}
body, err := DecodePortalBlockBodyBytes(content)
if err != nil {
h.log.Error("decodePortalBlockBodyBytes failed", "content", hexutil.Encode(content), "err", err)
continue
}
err = validateBlockBody(body, header)
if err != nil {
h.log.Error("validateBlockBody failed", "header", "err", err)
continue
}
err = h.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
h.log.Error("failed to store content in getBlockBody", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content))
}
return body, nil
}
return nil, storage.ErrContentNotFound
}
func (h *Network) GetReceipts(blockHash []byte) ([]*types.Receipt, error) {
header, err := h.GetBlockHeader(blockHash)
if err != nil {
return nil, err
}
contentKey := newContentKey(ReceiptsType, blockHash).encode()
contentId := h.portalProtocol.ToContentId(contentKey)
if !h.portalProtocol.InRange(contentId) {
return nil, ErrContentOutOfRange
}
res, err := h.portalProtocol.Get(contentKey, contentId)
// other error
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
return nil, err
}
// no error
if err == nil {
portalReceipte := new(PortalReceipts)
err := portalReceipte.UnmarshalSSZ(res)
if err != nil {
return nil, err
}
receipts, err := FromPortalReceipts(portalReceipte)
return receipts, err
}
// no content in local storage
for retries := 0; retries < requestRetries; retries++ {
content, _, err := h.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
h.log.Error("getReceipts failed", "contentKey", hexutil.Encode(contentKey), "err", err)
continue
}
receipts, err := ValidatePortalReceiptsBytes(content, header.ReceiptHash.Bytes())
if err != nil {
h.log.Error("getReceipts failed", "err", err)
continue
}
err = h.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
h.log.Error("failed to store content in getReceipts", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content))
}
return receipts, nil
}
return nil, storage.ErrContentNotFound
}
func (h *Network) verifyHeader(header *types.Header, proof BlockHeaderProof) (bool, error) {
return h.masterAccumulator.VerifyHeader(*header, proof)
}
func ValidateBlockBodyBytes(bodyBytes []byte, header *types.Header) (*types.Body, error) {
// TODO check shanghai, pos and legacy block
body, err := DecodePortalBlockBodyBytes(bodyBytes)
if err != nil {
return nil, err
}
err = validateBlockBody(body, header)
return body, err
}
func DecodePortalBlockBodyBytes(bodyBytes []byte) (*types.Body, error) {
blockBodyShanghai := new(PortalBlockBodyShanghai)
err := blockBodyShanghai.UnmarshalSSZ(bodyBytes)
if err == nil {
return FromPortalBlockBodyShanghai(blockBodyShanghai)
}
blockBodyLegacy := new(BlockBodyLegacy)
err = blockBodyLegacy.UnmarshalSSZ(bodyBytes)
if err == nil {
return FromBlockBodyLegacy(blockBodyLegacy)
}
return nil, errors.New("all portal block body decodings failed")
}
func validateBlockBody(body *types.Body, header *types.Header) error {
if hash := types.CalcUncleHash(body.Uncles); !bytes.Equal(hash[:], header.UncleHash.Bytes()) {
return ErrUnclesHashIsNotEqual
}
if hash := types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)); !bytes.Equal(hash[:], header.TxHash.Bytes()) {
return ErrTxHashIsNotEqual
}
if body.Withdrawals == nil {
return nil
}
if hash := types.DeriveSha(types.Withdrawals(body.Withdrawals), trie.NewStackTrie(nil)); !bytes.Equal(hash[:], header.WithdrawalsHash.Bytes()) {
return ErrWithdrawalHashIsNotEqual
}
return nil
}
// EncodeBlockBody encode types.Body to ssz bytes
func EncodeBlockBody(body *types.Body) ([]byte, error) {
if len(body.Withdrawals) > 0 {
blockShanghai, err := toPortalBlockBodyShanghai(body)
if err != nil {
return nil, err
}
return blockShanghai.MarshalSSZ()
} else {
legacyBlock, err := toBlockBodyLegacy(body)
if err != nil {
return nil, err
}
return legacyBlock.MarshalSSZ()
}
}
// toPortalBlockBodyShanghai convert types.Body to PortalBlockBodyShanghai
func toPortalBlockBodyShanghai(b *types.Body) (*PortalBlockBodyShanghai, error) {
legacy, err := toBlockBodyLegacy(b)
if err != nil {
return nil, err
}
withdrawals := make([][]byte, 0, len(b.Withdrawals))
for _, w := range b.Withdrawals {
b, err := rlp.EncodeToBytes(w)
if err != nil {
return nil, err
}
withdrawals = append(withdrawals, b)
}
return &PortalBlockBodyShanghai{Transactions: legacy.Transactions, Uncles: legacy.Uncles, Withdrawals: withdrawals}, nil
}
// toBlockBodyLegacy convert types.Body to BlockBodyLegacy
func toBlockBodyLegacy(b *types.Body) (*BlockBodyLegacy, error) {
txs := make([][]byte, 0, len(b.Transactions))
for _, tx := range b.Transactions {
txBytes, err := rlp.EncodeToBytes(tx)
if err != nil {
return nil, err
}
txs = append(txs, txBytes)
}
uncleBytes, err := rlp.EncodeToBytes(b.Uncles)
if err != nil {
return nil, err
}
return &BlockBodyLegacy{Uncles: uncleBytes, Transactions: txs}, err
}
// FromPortalBlockBodyShanghai convert PortalBlockBodyShanghai to types.Body
func FromPortalBlockBodyShanghai(b *PortalBlockBodyShanghai) (*types.Body, error) {
transactions := make([]*types.Transaction, 0, len(b.Transactions))
for _, t := range b.Transactions {
tran := new(types.Transaction)
err := tran.UnmarshalBinary(t)
if err != nil {
return nil, err
}
transactions = append(transactions, tran)
}
uncles := make([]*types.Header, 0, len(b.Uncles))
err := rlp.DecodeBytes(b.Uncles, &uncles)
withdrawals := make([]*types.Withdrawal, 0, len(b.Withdrawals))
for _, w := range b.Withdrawals {
withdrawal := new(types.Withdrawal)
err := rlp.DecodeBytes(w, withdrawal)
if err != nil {
return nil, err
}
withdrawals = append(withdrawals, withdrawal)
}
return &types.Body{
Uncles: uncles,
Transactions: transactions,
Withdrawals: withdrawals,
}, err
}
// FromBlockBodyLegacy convert BlockBodyLegacy to types.Body
func FromBlockBodyLegacy(b *BlockBodyLegacy) (*types.Body, error) {
transactions := make([]*types.Transaction, 0, len(b.Transactions))
for _, t := range b.Transactions {
tran := new(types.Transaction)
err := tran.UnmarshalBinary(t)
if err != nil {
return nil, err
}
transactions = append(transactions, tran)
}
uncles := make([]*types.Header, 0, len(b.Uncles))
err := rlp.DecodeBytes(b.Uncles, &uncles)
return &types.Body{
Uncles: uncles,
Transactions: transactions,
}, err
}
// FromPortalReceipts convert PortalReceipts to types.Receipt
func FromPortalReceipts(r *PortalReceipts) ([]*types.Receipt, error) {
res := make([]*types.Receipt, 0, len(r.Receipts))
for _, reci := range r.Receipts {
recipt := new(types.Receipt)
err := recipt.UnmarshalBinary(reci)
if err != nil {
return nil, err
}
res = append(res, recipt)
}
return res, nil
}
func ValidatePortalReceiptsBytes(receiptBytes, receiptsRoot []byte) ([]*types.Receipt, error) {
portalReceipts := new(PortalReceipts)
err := portalReceipts.UnmarshalSSZ(receiptBytes)
if err != nil {
return nil, err
}
receipts, err := FromPortalReceipts(portalReceipts)
if err != nil {
return nil, err
}
root := types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil))
if !bytes.Equal(root[:], receiptsRoot) {
return nil, errors.New("receipt root is not equal to the header.ReceiptHash")
}
return receipts, nil
}
func EncodeReceipts(receipts []*types.Receipt) ([]byte, error) {
portalReceipts, err := ToPortalReceipts(receipts)
if err != nil {
return nil, err
}
return portalReceipts.MarshalSSZ()
}
// ToPortalReceipts convert types.Receipt to PortalReceipts
func ToPortalReceipts(receipts []*types.Receipt) (*PortalReceipts, error) {
res := make([][]byte, 0, len(receipts))
for _, r := range receipts {
b, err := r.MarshalBinary()
if err != nil {
return nil, err
}
res = append(res, b)
}
return &PortalReceipts{Receipts: res}, nil
}
func (h *Network) processContentLoop(ctx context.Context) {
contentChan := h.portalProtocol.GetContent()
for {
select {
case <-ctx.Done():
return
case contentElement := <-contentChan:
err := h.validateContents(contentElement.ContentKeys, contentElement.Contents)
if err != nil {
h.log.Error("validate content failed", "err", err)
continue
}
go func(ctx context.Context) {
select {
case <-ctx.Done():
return
default:
var gossippedNum int
gossippedNum, err = h.portalProtocol.Gossip(&contentElement.Node, contentElement.ContentKeys, contentElement.Contents)
h.log.Trace("gossippedNum", "gossippedNum", gossippedNum)
if err != nil {
h.log.Error("gossip failed", "err", err)
return
}
}
}(ctx)
}
}
}
func (h *Network) validateContent(contentKey []byte, content []byte) error {
switch ContentType(contentKey[0]) {
case BlockHeaderType:
headerWithProof, err := DecodeBlockHeaderWithProof(content)
if err != nil {
return err
}
header, err := DecodeBlockHeader(headerWithProof.Header)
if err != nil {
return err
}
if !bytes.Equal(header.Hash().Bytes(), contentKey[1:]) {
return ErrInvalidBlockHash
}
valid, err := h.verifyHeader(header, *headerWithProof.Proof)
if err != nil {
return err
}
if !valid {
return ErrHeaderWithProofIsInvalid
}
return err
case BlockBodyType:
header, err := h.GetBlockHeader(contentKey[1:])
if err != nil {
return err
}
_, err = ValidateBlockBodyBytes(content, header)
return err
case ReceiptsType:
header, err := h.GetBlockHeader(contentKey[1:])
if err != nil {
return err
}
if bytes.Equal(header.ReceiptHash.Bytes(), emptyReceiptHash) {
if len(content) > 0 {
return fmt.Errorf("content should be empty, but received %v", content)
}
return nil
}
_, err = ValidatePortalReceiptsBytes(content, header.ReceiptHash.Bytes())
return err
case BlockHeaderNumberType:
headerWithProof, err := DecodeBlockHeaderWithProof(content)
if err != nil {
return err
}
header, err := DecodeBlockHeader(headerWithProof.Header)
if err != nil {
return err
}
blockNumber := view.Uint64View(0)
err = blockNumber.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey[1:]), uint64(len(contentKey[1:]))))
if err != nil {
return err
}
if header.Number.Cmp(big.NewInt(int64(blockNumber))) != 0 {
return ErrInvalidBlockNumber
}
valid, err := h.verifyHeader(header, *headerWithProof.Proof)
if err != nil {
return err
}
if !valid {
return ErrHeaderWithProofIsInvalid
}
return err
}
return errors.New("unknown content type")
}
func (h *Network) validateContents(contentKeys [][]byte, contents [][]byte) error {
for i, content := range contents {
contentKey := contentKeys[i]
err := h.validateContent(contentKey, content)
if err != nil {
h.log.Error("content validate failed", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content), "err", err)
return fmt.Errorf("content validate failed with content key %x and content %x", contentKey, content)
}
contentId := h.portalProtocol.ToContentId(contentKey)
_ = h.portalProtocol.Put(contentKey, contentId, content)
}
return nil
}
func ValidateBlockHeaderBytes(headerBytes []byte, blockHash []byte) (*types.Header, error) {
header := new(types.Header)
err := rlp.DecodeBytes(headerBytes, header)
if err != nil {
return nil, err
}
hash := header.Hash()
if !bytes.Equal(hash[:], blockHash) {
return nil, ErrInvalidBlockHash
}
return header, nil
}
func DecodeBlockHeader(headerBytes []byte) (*types.Header, error) {
header := new(types.Header)
err := rlp.DecodeBytes(headerBytes, header)
if err != nil {
return nil, err
}
return header, nil
}
func DecodeBlockHeaderWithProof(content []byte) (*BlockHeaderWithProof, error) {
headerWithProof := new(BlockHeaderWithProof)
err := headerWithProof.UnmarshalSSZ(content)
return headerWithProof, err
}
func decodeEpochAccumulator(data []byte) (*EpochAccumulator, error) {
epochAccu := new(EpochAccumulator)
err := epochAccu.UnmarshalSSZ(data)
return epochAccu, err
}

View file

@ -1,384 +0,0 @@
package history
import (
"bytes"
"context"
"crypto/sha256"
_ "embed"
"encoding/json"
"math/big"
"net"
"os"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
//go:embed testdata/shanghaibody.txt
var bodyData string
//go:embed testdata/epoch.txt
var epochAccuHex string
func ContentId(contentKey []byte) []byte {
digest := sha256.Sum256(contentKey)
return digest[:]
}
func TestValidateHeader(t *testing.T) {
entrys, err := parseBlockHeaderKeyContent()
require.NoError(t, err)
historyNetwork, err := genHistoryNetwork(":7891", nil)
require.NoError(t, err)
for _, entry := range entrys {
err = historyNetwork.validateContent(entry.key, entry.value)
require.NoError(t, err)
headerWithProof, err := DecodeBlockHeaderWithProof(entry.value)
require.NoError(t, err)
// invalid blockhash
_, err = ValidateBlockHeaderBytes(headerWithProof.Header, entry.key)
require.Equal(t, ErrInvalidBlockHash, err)
header, err := ValidateBlockHeaderBytes(headerWithProof.Header, entry.key[1:])
require.NoError(t, err)
// wrong header number
header.Number = big.NewInt(0).Add(header.Number, big.NewInt(122))
valid, err := historyNetwork.verifyHeader(header, *headerWithProof.Proof)
require.False(t, valid)
require.NoError(t, err)
}
}
func TestReceiptsAndBody(t *testing.T) {
entryMap, err := parseDataForBlock("block_14764013.json")
require.NoError(t, err)
testReceiptsAndBody(entryMap, t)
entryMap, err = parseDataForBlock("block_8951059.json")
require.NoError(t, err)
testReceiptsAndBody(entryMap, t)
}
func testReceiptsAndBody(entryMap map[string]contentEntry, t *testing.T) {
historyNetwork, err := genHistoryNetwork(":7893", nil)
require.NoError(t, err)
defer historyNetwork.Stop()
headerEntry := entryMap["header"]
// validateContents will store the content
err = historyNetwork.validateContents([][]byte{headerEntry.key}, [][]byte{headerEntry.value})
require.NoError(t, err)
bodyEntry := entryMap["body"]
err = historyNetwork.validateContent(bodyEntry.key, bodyEntry.value)
require.NoError(t, err)
receiptsEntry := entryMap["receipts"]
err = historyNetwork.validateContent(receiptsEntry.key, receiptsEntry.value)
require.NoError(t, err)
// test for portalReceipts encode and decode
portalReceipts := new(PortalReceipts)
err = portalReceipts.UnmarshalSSZ(receiptsEntry.value)
require.NoError(t, err)
portalBytes, err := portalReceipts.MarshalSSZ()
require.NoError(t, err)
require.True(t, bytes.Equal(portalBytes, receiptsEntry.value))
}
func TestPortalBlockShanghai(t *testing.T) {
bodyBytes, err := hexutil.Decode(bodyData)
require.NoError(t, err)
body, err := DecodePortalBlockBodyBytes(bodyBytes)
require.NoError(t, err)
require.True(t, len(body.Withdrawals) > 0)
}
func TestValidateEpochAccu(t *testing.T) {
if is32Bits() {
return
}
historyNetwork, err := genHistoryNetwork(":7892", nil)
require.NoError(t, err)
epochAccuBytes, err := hexutil.Decode(epochAccuHex)
require.NoError(t, err)
epochAccu, err := decodeEpochAccumulator(epochAccuBytes)
require.NoError(t, err)
epochRoot, err := epochAccu.HashTreeRoot()
require.NoError(t, err)
root := MixInLength(epochRoot, epochSize)
require.True(t, historyNetwork.masterAccumulator.Contains(root))
}
func TestGetContentByKey(t *testing.T) {
historyNetwork1, err := genHistoryNetwork(":7895", nil)
require.NoError(t, err)
historyNetwork2, err := genHistoryNetwork(":7896", []*enode.Node{historyNetwork1.portalProtocol.Self()})
require.NoError(t, err)
// wait node start
time.Sleep(10 * time.Second)
entryMap, err := parseDataForBlock("block_14764013.json")
require.NoError(t, err)
headerEntry := entryMap["header"]
// test GetBlockHeader
// no content
header, err := historyNetwork2.GetBlockHeader(headerEntry.key[1:])
require.Error(t, err)
require.Nil(t, header)
contentId := historyNetwork1.portalProtocol.ToContentId(headerEntry.key)
err = historyNetwork1.portalProtocol.Put(headerEntry.key, contentId, headerEntry.value)
require.NoError(t, err)
// get content from historyNetwork1
header, err = historyNetwork2.GetBlockHeader(headerEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, header)
// get content from local
header, err = historyNetwork2.GetBlockHeader(headerEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, header)
// test GetBlockBody
// no content
bodyEntry := entryMap["body"]
body, err := historyNetwork2.GetBlockBody(bodyEntry.key[1:])
require.Error(t, err)
require.Nil(t, body)
contentId = historyNetwork1.portalProtocol.ToContentId(bodyEntry.key)
err = historyNetwork1.portalProtocol.Put(bodyEntry.key, contentId, bodyEntry.value)
require.NoError(t, err)
// get content from historyNetwork1
body, err = historyNetwork2.GetBlockBody(bodyEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, body)
// get content from local
body, err = historyNetwork2.GetBlockBody(bodyEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, body)
// test GetBlockReceipts
// no content
receiptsEntry := entryMap["receipts"]
receipts, err := historyNetwork2.GetReceipts(receiptsEntry.key[1:])
require.Error(t, err)
require.Nil(t, receipts)
contentId = historyNetwork1.portalProtocol.ToContentId(receiptsEntry.key)
err = historyNetwork1.portalProtocol.Put(receiptsEntry.key, contentId, receiptsEntry.value)
require.NoError(t, err)
// get content from historyNetwork1
receipts, err = historyNetwork2.GetReceipts(receiptsEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, receipts)
// get content from local
receipts, err = historyNetwork2.GetReceipts(receiptsEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, receipts)
if is32Bits() {
return
}
headerNumberEntry := entryMap["headerBlock"]
// test GetBlockHeader
// no content
header, err = historyNetwork2.GetBlockHeader(headerNumberEntry.key[1:])
require.Error(t, err)
require.Nil(t, header)
contentId = historyNetwork1.portalProtocol.ToContentId(headerNumberEntry.key)
err = historyNetwork1.portalProtocol.Put(headerEntry.key, contentId, headerEntry.value)
require.NoError(t, err)
// get content from historyNetwork1
header, err = historyNetwork2.GetBlockHeader(headerEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, header)
// get content from local
header, err = historyNetwork2.GetBlockHeader(headerEntry.key[1:])
require.NoError(t, err)
require.NotNil(t, header)
}
type Entry struct {
ContentKey string `yaml:"content_key"`
ContentValue string `yaml:"content_value"`
}
func TestValidateContents(t *testing.T) {
file, err := os.ReadFile("./testdata/hive_gossip.yaml")
require.NoError(t, err)
entries := make([]Entry, 0)
err = yaml.Unmarshal(file, &entries)
require.NoError(t, err)
historyNetwork, err := genHistoryNetwork(":7897", nil)
require.NoError(t, err)
keys := make([][]byte, 0)
values := make([][]byte, 0)
for _, entry := range entries {
keys = append(keys, hexutil.MustDecode(entry.ContentKey))
values = append(values, hexutil.MustDecode(entry.ContentValue))
}
err = historyNetwork.validateContents(keys, values)
require.NoError(t, err)
}
func TestValidateContentForCancun(t *testing.T) {
master, err := NewMasterAccumulator()
require.NoError(t, err)
historyNetwork := &Network{
masterAccumulator: &master,
}
key := hexutil.MustDecode("0x002149dec8fb41655fb32437a011294d7c99babb08f6adaf0bb39427d99f03521d")
value := hexutil.MustDecode("0x0800000060020000f90255a087bac4b2f672ada2dc2c840dc9c6f6ee0c334bd1a56a985b9e7ab8ce6bbd7dd4a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479495222290dd7278aa3ddd389cc1e1d165cc4bafe5a0e55e04845685845dced4651a6f3d0e50b356ff4c43a659aa2699db0e7b0ea463a0e93c75c5ad3c88ee280f383f4f4a17f2852640f06ebc6397e2012108b890e7d4a015cfe3074ab21cc714aaa33c951877467f7fd3c32a8ba3331d50b6451c006379b901000121100a000000020000020080201000084080000202008000000000080000000040008000000020000000020020000002010000080020000440040000280100200001080000800c080000090000002000000101204405000000000008201000000000000000000000009000000000004000000800000440900050102008060002000040000000000000000001000800000000204100080806000040000000000220006050002000000000808200020004040000000001040340001000080000000000030008800000a000000000100000002000040010100000000a00000000001320020004002000000200000000000000520012040000000000000010040080840128fca98401c9c3808310f22c8465f8821b8f6265617665726275696c642e6f7267a00b93e63eedf5c0d976e80761a4869868f3d507551095a7ae9db02d58ccd88200880000000000000000850b978050aca03d4fc5f03a4a2fac8ab5cf1050b840ae1ff004bcdf9dac16ec5f5412d2b6b78f8080a00241b464d0c5f42d85568d6611b76f84f393320981227266c2686428ca28778700")
err = historyNetwork.validateContent(key, value)
require.NoError(t, err)
}
type contentEntry struct {
key []byte
value []byte
}
func parseBlockHeaderKeyContent() ([]contentEntry, error) {
headWithProofBytes, err := os.ReadFile("./testdata/header_with_proofs.json")
if err != nil {
return nil, err
}
headerMap := make(map[string]map[string]string)
err = json.Unmarshal(headWithProofBytes, &headerMap)
if err != nil {
return nil, err
}
res := make([]contentEntry, 0)
for _, v := range headerMap {
entry := contentEntry{}
val := v["value"]
bytes, err := hexutil.Decode(val)
if err != nil {
return nil, err
}
entry.value = bytes
key := v["content_key"]
keyBytes, err := hexutil.Decode(key)
if err != nil {
return nil, err
}
entry.key = keyBytes
res = append(res, entry)
}
return res, nil
}
func genHistoryNetwork(addr string, bootNodes []*enode.Node) (*Network, error) {
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, true))
slogVerbosity := log.FromLegacyLevel(5)
glogger.Verbosity(slogVerbosity)
log.SetDefault(log.NewLogger(glogger))
conf := portalwire.DefaultPortalProtocolConfig()
if addr != "" {
conf.ListenAddr = addr
}
if bootNodes != nil {
conf.BootstrapNodes = bootNodes
}
addr1, err := net.ResolveUDPAddr("udp", conf.ListenAddr)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr1)
if err != nil {
return nil, err
}
privKey, err := crypto.GenerateKey()
if err != nil {
panic("couldn't generate key: " + err.Error())
}
discCfg := discover.Config{
PrivateKey: privKey,
NetRestrict: conf.NetRestrict,
Bootnodes: conf.BootstrapNodes,
}
nodeDB, err := enode.OpenDB(conf.NodeDBPath)
if err != nil {
return nil, err
}
localNode := enode.NewLocalNode(nodeDB, privKey)
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(portalwire.Tag)
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err
}
contentQueue := make(chan *portalwire.ContentElement, 50)
utpSocket := portalwire.NewPortalUtp(context.Background(), conf, discV5, conn)
portalProtocol, err := portalwire.NewPortalProtocol(conf, portalwire.History, privKey, conn, localNode, discV5, utpSocket, &storage.MockStorage{Db: make(map[string][]byte)}, contentQueue)
if err != nil {
return nil, err
}
accu, err := NewMasterAccumulator()
if err != nil {
return nil, err
}
err = portalProtocol.Start()
if err != nil {
return nil, err
}
return NewHistoryNetwork(portalProtocol, &accu), nil
}
func parseDataForBlock(fileName string) (map[string]contentEntry, error) {
content, err := os.ReadFile("./testdata/block_14764013.json")
if err != nil {
return nil, err
}
contentMap := make(map[string]map[string]string)
_ = json.Unmarshal(content, &contentMap)
res := make(map[string]contentEntry)
for key, val := range contentMap {
entry := contentEntry{}
contentKey := val["content_key"]
entry.key, err = hexutil.Decode(contentKey)
if err != nil {
return nil, err
}
entry.value, err = hexutil.Decode(val["content_value"])
if err != nil {
return nil, err
}
res[key] = entry
}
return res, nil
}
func is32Bits() bool {
return (32 << (^uint(0) >> 63)) == 32
}

View file

@ -1,443 +0,0 @@
package history
//
//import (
// "encoding/binary"
// "errors"
// "fmt"
// "path"
// "sync/atomic"
//
// "github.com/cockroachdb/pebble"
// "github.com/ethereum/go-ethereum/log"
// "github.com/ethereum/go-ethereum/metrics"
// "github.com/ethereum/go-ethereum/p2p/enode"
// "github.com/ethereum/go-ethereum/portalnetwork/storage"
// "github.com/holiman/uint256"
//)
//
//const (
// contentDeletionFraction = 0.05
// prefixContent = byte(0x01) // prefixContent + distance + contentId -> content
// prefixDistanceSize = byte(0x02) // prefixDistanceSize + distance -> total size
//)
//
//type ContentStorage struct {
// nodeId enode.ID
// storageCapacityInBytes uint64
// radius atomic.Value
// db *pebble.DB
// log log.Logger
//}
//
//func NewHistoryStorage(config storage.PortalStorageConfig) (storage.ContentStorage, error) {
// dbPath := path.Join(config.DataDir, config.NetworkName)
//
// opts := &pebble.Options{
// MaxOpenFiles: 1000,
// }
//
// db, err := pebble.Open(dbPath, opts)
// if err != nil {
// return nil, err
// }
//
// cs := &ContentStorage{
// nodeId: config.NodeId,
// db: db,
// storageCapacityInBytes: config.StorageCapacityMB * 1000000,
// log: log.New("storage", config.NetworkName),
// }
// cs.radius.Store(storage.MaxDistance)
// cs.setRadiusToFarthestDistance()
//
// return cs, nil
//}
//
//func makeKey(prefix byte, distance []byte, contentId []byte) []byte {
// if contentId == nil {
// key := make([]byte, 1+len(distance))
// key[0] = prefix
// copy(key[1:], distance)
// return key
// }
// key := make([]byte, 1+len(distance)+len(contentId))
// key[0] = prefix
// copy(key[1:], distance)
// copy(key[1+len(distance):], contentId)
// return key
//}
//
//func (p *ContentStorage) Put(contentKey []byte, contentId []byte, content []byte) error {
// distance := xor(contentId, p.nodeId[:])
// key := makeKey(prefixContent, distance, contentId)
//
// batch := p.db.NewBatch()
// defer batch.Close()
//
// // Update content
// if err := batch.Set(key, content, pebble.Sync); err != nil {
// return err
// }
//
// // Update distance size index
// sizeKey := makeKey(prefixDistanceSize, distance, nil)
// var currentSize uint64
// if value, closer, err := p.db.Get(sizeKey); err == nil {
// currentSize = binary.BigEndian.Uint64(value)
// closer.Close()
// }
//
// newSize := currentSize + uint64(len(content))
// sizeBytes := make([]byte, 8)
// binary.BigEndian.PutUint64(sizeBytes, newSize)
//
// if err := batch.Set(sizeKey, sizeBytes, pebble.Sync); err != nil {
// return err
// }
//
// if err := batch.Commit(pebble.Sync); err != nil {
// return err
// }
//
// if size, _ := p.UsedSize(); size > p.storageCapacityInBytes {
// if _, err := p.deleteContentFraction(contentDeletionFraction); err != nil {
// p.log.Warn("failed to delete oversize content", "err", err)
// }
// }
//
// if metrics.Enabled {
// portalStorageMetrics.EntriesCount.Inc(1)
// portalStorageMetrics.ContentStorageUsage.Inc(int64(len(content)))
// }
//
// return nil
//}
//
//func (p *ContentStorage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
// distance := xor(contentId, p.nodeId[:])
// key := makeKey(prefixContent, distance, contentId)
//
// value, closer, err := p.db.Get(key)
// if err == pebble.ErrNotFound {
// return nil, storage.ErrContentNotFound
// }
// if err != nil {
// return nil, err
// }
// defer closer.Close()
//
// return value, nil
//}
//
//func (p *ContentStorage) deleteContentFraction(fraction float64) (deleteCount int, err error) {
// if fraction <= 0 || fraction >= 1 {
// return 0, errors.New("fraction should be between 0 and 1")
// }
//
// totalSize, err := p.ContentSize()
// if err != nil {
// return 0, err
// }
//
// targetSize := uint64(float64(totalSize) * fraction)
// deletedSize := uint64(0)
// count := 0
//
// iter := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixContent},
// UpperBound: []byte{prefixContent + 1},
// })
// defer iter.Close()
//
// batch := p.db.NewBatch()
// defer batch.Close()
//
// for iter.Last(); iter.Valid() && deletedSize < targetSize; iter.Prev() {
// key := iter.Key()
// value := iter.Value()
// distance := key[1:33]
//
// // Delete content
// if err := batch.Delete(key, nil); err != nil {
// return count, err
// }
//
// // Update distance size index
// sizeKey := makeKey(prefixDistanceSize, distance, nil)
// var currentSize uint64
// sizeValue, closer, err := p.db.Get(sizeKey)
// if err == nil {
// currentSize = binary.BigEndian.Uint64(sizeValue)
// closer.Close()
// }
//
// newSize := currentSize - uint64(len(value))
// if newSize == 0 {
// if err := batch.Delete(sizeKey, nil); err != nil {
// return count, err
// }
// } else {
// sizeBytes := make([]byte, 8)
// binary.BigEndian.PutUint64(sizeBytes, newSize)
// if err := batch.Set(sizeKey, sizeBytes, nil); err != nil {
// return count, err
// }
// }
//
// deletedSize += uint64(len(value))
// count++
//
// if batch.Len() >= 1000 {
// if err := batch.Commit(pebble.Sync); err != nil {
// return count, err
// }
// batch = p.db.NewBatch()
// }
// }
// if batch.Len() > 0 {
// if err := batch.Commit(pebble.Sync); err != nil {
// return count, err
// }
// }
//
// if iter.Valid() {
// key := iter.Key()
// distance := key[1:33]
// dis := uint256.NewInt(0)
// if err := dis.UnmarshalSSZ(distance); err != nil {
// return count, err
// }
// p.radius.Store(dis)
// }
//
// return count, nil
//}
//
//func (p *ContentStorage) UsedSize() (uint64, error) {
// var totalSize uint64
// iter := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixDistanceSize},
// UpperBound: []byte{prefixDistanceSize + 1},
// })
// defer iter.Close()
//
// for iter.First(); iter.Valid(); iter.Next() {
// size := binary.BigEndian.Uint64(iter.Value())
// totalSize += size
// }
//
// return totalSize, nil
//}
//
//func (p *ContentStorage) ContentSize() (uint64, error) {
// return p.UsedSize()
//}
//
//func (p *ContentStorage) ContentCount() (uint64, error) {
// var count uint64
// iter, _ := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixContent},
// UpperBound: []byte{prefixContent + 1},
// })
// defer iter.Close()
//
// for iter.First(); iter.Valid(); iter.Next() {
// count++
// }
//
// return count, nil
//}
//
//func (p *ContentStorage) Radius() *uint256.Int {
// radius := p.radius.Load()
// val := radius.(*uint256.Int)
// return val
//}
//func (p *ContentStorage) GetLargestDistance() (*uint256.Int, error) {
// iter := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixContent},
// UpperBound: []byte{prefixContent + 1},
// })
// defer iter.Close()
//
// if !iter.Last() {
// return nil, fmt.Errorf("no content found")
// }
//
// key := iter.Key()
// distance := key[1:33]
//
// res := uint256.NewInt(0)
// err := res.UnmarshalSSZ(distance)
// return res, err
//}
//
//func (p *ContentStorage) EstimateNewRadius(currentRadius *uint256.Int) (*uint256.Int, error) {
// currrentSize, err := p.UsedSize()
// if err != nil {
// return nil, err
// }
//
// sizeRatio := currrentSize / p.storageCapacityInBytes
// if sizeRatio > 0 {
// newRadius := new(uint256.Int).Div(currentRadius, uint256.NewInt(sizeRatio))
//
// if metrics.Enabled {
// ratio := new(uint256.Int).Mul(newRadius, uint256.NewInt(100))
// ratio.Mod(ratio, storage.MaxDistance)
// portalStorageMetrics.RadiusRatio.Update(ratio.Float64() / 100)
// }
//
// return newRadius, nil
// }
// return currentRadius, nil
//}
//
//func (p *ContentStorage) setRadiusToFarthestDistance() {
// largestDistance, err := p.GetLargestDistance()
// if err != nil {
// p.log.Error("failed to get farthest distance", "err", err)
// return
// }
// p.radius.Store(largestDistance)
//}
//func (p *ContentStorage) ForcePrune(radius *uint256.Int) error {
// batch := p.db.NewBatch()
// defer batch.Close()
//
// iter := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixContent},
// UpperBound: []byte{prefixContent + 1},
// })
// defer iter.Close()
//
// var deletedSize int64
// deleteCount := 0
//
// for iter.First(); iter.Valid(); iter.Next() {
// key := iter.Key()
// value := iter.Value()
// distance := key[1:33]
//
// dis := uint256.NewInt(0)
// if err := dis.UnmarshalSSZ(distance); err != nil {
// return err
// }
//
// if dis.Cmp(radius) > 0 {
// // Delete content
// if err := batch.Delete(key, nil); err != nil {
// return err
// }
//
// // Update distance size index
// sizeKey := makeKey(prefixDistanceSize, distance, nil)
// var currentSize uint64
// if sizeValue, closer, err := p.db.Get(sizeKey); err == nil {
// currentSize = binary.BigEndian.Uint64(sizeValue)
// closer.Close()
// }
//
// newSize := currentSize - uint64(len(value))
// if newSize == 0 {
// if err := batch.Delete(sizeKey, nil); err != nil {
// return err
// }
// } else {
// sizeBytes := make([]byte, 8)
// binary.BigEndian.PutUint64(sizeBytes, newSize)
// if err := batch.Set(sizeKey, sizeBytes, nil); err != nil {
// return err
// }
// }
//
// deletedSize += int64(len(value))
// deleteCount++
// }
//
// if batch.Len() >= 1000 {
// if err := batch.Commit(pebble.Sync); err != nil {
// return err
// }
// batch = p.db.NewBatch()
// }
// }
// if batch.Len() > 0 {
// if err := batch.Commit(pebble.Sync); err != nil {
// return err
// }
// }
//
// if metrics.Enabled {
// portalStorageMetrics.EntriesCount.Dec(int64(deleteCount))
// portalStorageMetrics.ContentStorageUsage.Dec(deletedSize)
// }
//
// return nil
//}
//
//func (p *ContentStorage) ReclaimSpace() error {
// return p.db.Compact([]byte{prefixContent}, []byte{prefixContent + 1}, true)
//}
//
//func (p *ContentStorage) Close() error {
// return p.db.Close()
//}
//
//func (p *ContentStorage) SizeByKey(contentId []byte) (uint64, error) {
// distance := xor(contentId, p.nodeId[:])
// key := makeKey(prefixContent, distance, contentId)
//
// value, closer, err := p.db.Get(key)
// if err == pebble.ErrNotFound {
// return 0, nil
// }
// if err != nil {
// return 0, err
// }
// defer closer.Close()
//
// return uint64(len(value)), nil
//}
//
//func (p *ContentStorage) SizeByKeys(ids [][]byte) (uint64, error) {
// var totalSize uint64
//
// for _, id := range ids {
// size, err := p.SizeByKey(id)
// if err != nil {
// return 0, err
// }
// totalSize += size
// }
//
// return totalSize, nil
//}
//
//func (p *ContentStorage) SizeOutRadius(radius *uint256.Int) (uint64, error) {
// var totalSize uint64
//
// iter := p.db.NewIter(&pebble.IterOptions{
// LowerBound: []byte{prefixDistanceSize},
// UpperBound: []byte{prefixDistanceSize + 1},
// })
// defer iter.Close()
//
// for iter.First(); iter.Valid(); iter.Next() {
// key := iter.Key()
// distance := key[1:33]
//
// dis := uint256.NewInt(0)
// if err := dis.UnmarshalSSZ(distance); err != nil {
// return 0, err
// }
//
// if dis.Cmp(radius) > 0 {
// size := binary.BigEndian.Uint64(iter.Value())
// totalSize += size
// }
// }
//
// return totalSize, nil
//}

View file

@ -1,556 +0,0 @@
package history
import (
"bytes"
"database/sql"
"errors"
"fmt"
"math/big"
"os"
"path"
"strings"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
"github.com/mattn/go-sqlite3"
)
const (
sqliteName = "history.sqlite"
contentDeletionFraction = 0.05 // 5% of the content will be deleted when the storage capacity is hit and radius gets adjusted.
// SQLite Statements
createSql = `CREATE TABLE IF NOT EXISTS kvstore (
key BLOB PRIMARY KEY,
value BLOB
);`
getSql = "SELECT value FROM kvstore WHERE key = (?1);"
putSql = "INSERT OR REPLACE INTO kvstore (key, value) VALUES (?1, ?2);"
deleteSql = "DELETE FROM kvstore WHERE key = (?1);"
containSql = "SELECT 1 FROM kvstore WHERE key = (?1);"
getAllOrderedByDistanceSql = "SELECT key, length(value), xor(key, (?1)) as distance FROM kvstore ORDER BY distance DESC;"
getFarthestDistanceSql = "SELECT key, xor(key, (?1)) as distance FROM kvstore ORDER BY distance DESC Limit 1;"
deleteOutOfRadiusStmt = "DELETE FROM kvstore WHERE greater(xor(key, (?1)), (?2)) = 1"
XorFindFarthestQuery = `SELECT
xor(key, (?1)) as distance
FROM kvstore
ORDER BY distance DESC`
)
var _ storage.ContentStorage = &ContentStorage{}
var once sync.Once
type ContentStorage struct {
nodeId enode.ID
storageCapacityInBytes uint64
radius atomic.Value
sqliteDB *sql.DB
getStmt *sql.Stmt
putStmt *sql.Stmt
delStmt *sql.Stmt
containStmt *sql.Stmt
log log.Logger
}
var portalStorageMetrics *portalwire.PortalStorageMetrics
func xor(contentId, nodeId []byte) []byte {
// length of contentId maybe not 32bytes
padding := make([]byte, 32)
if len(contentId) != len(nodeId) {
copy(padding, contentId)
} else {
padding = contentId
}
res := make([]byte, len(padding))
for i := range padding {
res[i] = padding[i] ^ nodeId[i]
}
return res
}
// a > b return 1; a = b return 0; else return -1
func greater(a, b []byte) int {
return bytes.Compare(a, b)
}
func NewDB(dataDir string, network string) (*sql.DB, error) {
dbPath := path.Join(dataDir, network)
err := os.MkdirAll(dbPath, 0755)
if err != nil {
return nil, err
}
// avoid repeated register in tests
once.Do(func() {
sql.Register("sqlite3_custom", &sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
if err := conn.RegisterFunc("xor", xor, false); err != nil {
return err
}
if err := conn.RegisterFunc("greater", greater, false); err != nil {
return err
}
return nil
},
})
})
sqlDb, err := sql.Open("sqlite3_custom", path.Join(dbPath, fmt.Sprintf("%s.sqlite", network)))
return sqlDb, err
}
func NewHistoryStorage(config storage.PortalStorageConfig) (storage.ContentStorage, error) {
hs := &ContentStorage{
nodeId: config.NodeId,
sqliteDB: config.DB,
storageCapacityInBytes: config.StorageCapacityMB * 1000000,
log: log.New("storage", config.NetworkName),
}
hs.radius.Store(storage.MaxDistance)
err := hs.createTable()
if err != nil {
return nil, err
}
err = hs.initStmts()
// Check whether we already have data, and use it to set radius
hs.setRadiusToFarthestDistance()
// necessary to test NetworkName==history because state also initialize HistoryStorage
if strings.ToLower(config.NetworkName) == "history" {
portalStorageMetrics, err = portalwire.NewPortalStorageMetrics(config.NetworkName, config.DB)
if err != nil {
return nil, err
}
}
return hs, err
}
// Get the content according to the contentId
func (p *ContentStorage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
p.log.Trace("get content", "contentKey", hexutil.Encode(contentKey), "contentId", hexutil.Encode(contentId))
var res []byte
err := p.getStmt.QueryRow(contentId).Scan(&res)
if errors.Is(err, sql.ErrNoRows) {
return nil, storage.ErrContentNotFound
}
return res, err
}
type PutResult struct {
err error
pruned bool
count int
}
func (p *PutResult) Err() error {
return p.err
}
func (p *PutResult) Pruned() bool {
return p.pruned
}
func (p *PutResult) PrunedCount() int {
return p.count
}
func newPutResultWithErr(err error) PutResult {
return PutResult{
err: err,
}
}
func (p *ContentStorage) Radius() *uint256.Int {
radius := p.radius.Load()
val := radius.(*uint256.Int)
return val
}
func (p *ContentStorage) Put(contentKey []byte, contentId []byte, content []byte) error {
res := p.put(contentId, content)
return res.Err()
}
// Put saves the contentId and content
func (p *ContentStorage) put(contentId []byte, content []byte) PutResult {
_, err := p.putStmt.Exec(contentId, content)
if err != nil {
return newPutResultWithErr(err)
}
dbSize, err := p.UsedSize()
if err != nil {
return newPutResultWithErr(err)
}
if dbSize > p.storageCapacityInBytes {
count, err := p.deleteContentFraction(contentDeletionFraction)
//
if err != nil {
log.Warn("failed to delete oversize item")
return newPutResultWithErr(err)
}
return PutResult{pruned: true, count: count}
}
if metrics.Enabled {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(content)))
}
return PutResult{}
}
func (p *ContentStorage) Close() error {
err := p.getStmt.Close()
if err != nil {
return err
}
err = p.putStmt.Close()
if err != nil {
return err
}
err = p.delStmt.Close()
if err != nil {
return err
}
err = p.containStmt.Close()
if err != nil {
return err
}
return p.sqliteDB.Close()
}
func (p *ContentStorage) createTable() error {
stmt, err := p.sqliteDB.Prepare(createSql)
if err != nil {
return err
}
defer func(stat *sql.Stmt) {
if err = stat.Close(); err != nil {
p.log.Error("failed to close statement", "err", err)
}
}(stmt)
_, err = stmt.Exec()
return err
}
func (p *ContentStorage) initStmts() error {
var stat *sql.Stmt
var err error
if stat, err = p.sqliteDB.Prepare(getSql); err != nil {
return nil
}
p.getStmt = stat
if stat, err = p.sqliteDB.Prepare(putSql); err != nil {
return nil
}
p.putStmt = stat
if stat, err = p.sqliteDB.Prepare(deleteSql); err != nil {
return nil
}
p.delStmt = stat
if stat, err = p.sqliteDB.Prepare(containSql); err != nil {
return nil
}
p.containStmt = stat
return nil
}
// Size get database size, content size and similar
func (p *ContentStorage) Size() (uint64, error) {
sql := "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();"
return p.queryRowUint64(sql)
}
func (p *ContentStorage) UnusedSize() (uint64, error) {
sql := "SELECT freelist_count * page_size as size FROM pragma_freelist_count(), pragma_page_size();"
return p.queryRowUint64(sql)
}
// UsedSize = Size - UnusedSize
func (p *ContentStorage) UsedSize() (uint64, error) {
size, err := p.Size()
if err != nil {
return 0, err
}
unusedSize, err := p.UnusedSize()
if err != nil {
return 0, err
}
return size - unusedSize, err
}
// ContentCount return the total content count
func (p *ContentStorage) ContentCount() (uint64, error) {
sql := "SELECT COUNT(key) FROM kvstore;"
return p.queryRowUint64(sql)
}
func (p *ContentStorage) ContentSize() (uint64, error) {
sql := "SELECT SUM(length(value)) FROM kvstore"
return p.queryRowUint64(sql)
}
func (p *ContentStorage) SizeByKey(contentId []byte) (uint64, error) {
sql := "SELECT SUM( length(value) ) FROM kvstore WHERE key = " + string(contentId) + ";"
return p.queryRowUint64(sql)
}
func (p *ContentStorage) SizeByKeys(ids [][]byte) (uint64, error) {
sql := "SELECT SUM( length(value) ) FROM kvstore WHERE key IN (?" + strings.Repeat(", ?", len(ids)-1) + ");"
return p.queryRowUint64(sql)
}
func (p *ContentStorage) SizeOutRadius(radius *uint256.Int) (uint64, error) {
sql := "SELECT SUM( length(value) ) FROM kvstore WHERE greater(xor(key, (?1)), (?2)) = 1;"
var size uint64
err := p.sqliteDB.QueryRow(sql, p.nodeId[:], radius.Bytes()).Scan(&size)
return size, err
}
func (p *ContentStorage) queryRowUint64(sqlStr string) (uint64, error) {
// sql := "SELECT SUM(length(value)) FROM kvstore"
stmt, err := p.sqliteDB.Prepare(sqlStr)
if err != nil {
return 0, err
}
defer func(stat *sql.Stmt) {
if err = stat.Close(); err != nil {
p.log.Error("failed to close statement", "err", err)
}
}(stmt)
var res uint64
err = stmt.QueryRow().Scan(&res)
return res, err
}
// GetLargestDistance find the largest distance
func (p *ContentStorage) GetLargestDistance() (*uint256.Int, error) {
stmt, err := p.sqliteDB.Prepare(XorFindFarthestQuery)
if err != nil {
return nil, err
}
defer func(stat *sql.Stmt) {
if err = stat.Close(); err != nil {
p.log.Error("failed to close statement", "err", err)
}
}(stmt)
var distance []byte
err = stmt.QueryRow(p.nodeId[:]).Scan(&distance)
if err != nil {
return nil, err
}
res := uint256.NewInt(0)
err = res.UnmarshalSSZ(distance)
return res, err
}
// EstimateNewRadius calculates an estimated new radius based on the current radius, used size, and storage capacity.
// The method takes the currentRadius as input and returns the estimated new radius and an error (if any).
// It calculates the size ratio of usedSize to storageCapacityInBytes and adjusts the currentRadius accordingly.
// If the size ratio is greater than 0, it performs the adjustment; otherwise, it returns the currentRadius unchanged.
// The method returns an error if there is any issue in determining the used size.
func (p *ContentStorage) EstimateNewRadius(currentRadius *uint256.Int) (*uint256.Int, error) {
currrentSize, err := p.UsedSize()
if err != nil {
return nil, err
}
sizeRatio := currrentSize / p.storageCapacityInBytes
if sizeRatio > 0 {
bigFormat := new(big.Int).SetUint64(sizeRatio)
if metrics.Enabled {
newRadius := new(uint256.Int).Div(currentRadius, uint256.MustFromBig(bigFormat))
newRadius.Mul(newRadius, uint256.NewInt(100))
newRadius.Mod(newRadius, storage.MaxDistance)
portalStorageMetrics.RadiusRatio.Update(newRadius.Float64() / 100)
}
return new(uint256.Int).Div(currentRadius, uint256.MustFromBig(bigFormat)), nil
}
return currentRadius, nil
}
func (p *ContentStorage) setRadiusToFarthestDistance() {
rows, err := p.sqliteDB.Query(getFarthestDistanceSql, p.nodeId[:])
if err != nil {
p.log.Error("failed to query farthest distance ", "err", err)
return
}
defer func(rows *sql.Rows) {
if rows != nil {
return
}
err = rows.Close()
if err != nil {
p.log.Error("failed to close rows", "err", err)
}
}(rows)
if rows.Next() {
var contentId []byte
var distance []byte
err = rows.Scan(&contentId, &distance)
if err != nil {
p.log.Error("failed to scan rows for farthest distance", "err", err)
}
dis := uint256.NewInt(0)
err = dis.UnmarshalSSZ(distance)
if err != nil {
p.log.Error("failed to unmarshal ssz for farthest distance", "err", err)
}
p.radius.Store(dis)
}
}
func (p *ContentStorage) deleteContentFraction(fraction float64) (deleteCount int, err error) {
if fraction <= 0 || fraction >= 1 {
return deleteCount, errors.New("fraction should be between 0 and 1")
}
totalContentSize, err := p.ContentSize()
if err != nil {
return deleteCount, err
}
bytesToDelete := uint64(fraction * float64(totalContentSize))
// deleteElements := 0
deleteBytes := 0
rows, err := p.sqliteDB.Query(getAllOrderedByDistanceSql, p.nodeId[:])
if err != nil {
return deleteCount, err
}
defer func(rows *sql.Rows) {
err = rows.Close()
if err != nil {
p.log.Error("failed to close rows", "err", err)
}
}(rows)
idsToDelete := make([][]byte, 0)
for deleteBytes < int(bytesToDelete) && rows.Next() {
var contentId []byte
var payloadLen int
var distance []byte
err = rows.Scan(&contentId, &payloadLen, &distance)
if err != nil {
return deleteCount, err
}
idsToDelete = append(idsToDelete, contentId)
// err = p.del(contentId)
if err != nil {
return deleteCount, err
}
deleteBytes += payloadLen
deleteCount++
}
// set the largest distince
if rows.Next() {
var contentId []byte
var payloadLen int
var distance []byte
err = rows.Scan(&contentId, &payloadLen, &distance)
if err != nil {
return 0, err
}
dis := uint256.NewInt(0)
err = dis.UnmarshalSSZ(distance)
if err != nil {
return 0, err
}
p.radius.Store(dis)
if metrics.Enabled {
dis.Mul(dis, uint256.NewInt(100))
dis.Mod(dis, storage.MaxDistance)
portalStorageMetrics.RadiusRatio.Update(dis.Float64() / 100)
}
}
// row must close first, or database is locked
// rows.Close() can call multi times
err = rows.Close()
if err != nil {
return 0, err
}
err = p.batchDel(idsToDelete)
return
}
func (p *ContentStorage) del(contentId []byte) error {
var sizeDel uint64
var err error
if metrics.Enabled {
sizeDel, err = p.SizeByKey(contentId)
if err != nil {
return err
}
}
_, err = p.delStmt.Exec(contentId)
if metrics.Enabled && err == nil {
portalStorageMetrics.EntriesCount.Dec(1)
portalStorageMetrics.ContentStorageUsage.Dec(int64(sizeDel))
}
return err
}
func (p *ContentStorage) batchDel(ids [][]byte) error {
var sizeDel uint64
var err error
if metrics.Enabled {
sizeDel, err = p.SizeByKeys(ids)
if err != nil {
return err
}
}
query := "DELETE FROM kvstore WHERE key IN (?" + strings.Repeat(", ?", len(ids)-1) + ")"
args := make([]interface{}, len(ids))
for i, id := range ids {
args[i] = id
}
// delete items
_, err = p.sqliteDB.Exec(query, args...)
if metrics.Enabled && err == nil {
portalStorageMetrics.EntriesCount.Dec(int64(len(args)))
portalStorageMetrics.ContentStorageUsage.Dec(int64(sizeDel))
}
return err
}
// ReclaimSpace reclaims space in the ContentStorage's SQLite database by performing a VACUUM operation.
// It returns an error if the VACUUM operation encounters any issues.
func (p *ContentStorage) ReclaimSpace() error {
_, err := p.sqliteDB.Exec("VACUUM;")
return err
}
func (p *ContentStorage) deleteContentOutOfRadius(radius *uint256.Int) error {
var sizeDel uint64
var err error
if metrics.Enabled {
sizeDel, err = p.SizeOutRadius(radius)
if err != nil {
return err
}
}
res, err := p.sqliteDB.Exec(deleteOutOfRadiusStmt, p.nodeId[:], radius.Bytes())
if err != nil {
return err
}
count, err := res.RowsAffected()
p.log.Trace("delete items", "count", count)
if metrics.Enabled && err == nil {
portalStorageMetrics.EntriesCount.Dec(count)
portalStorageMetrics.ContentStorageUsage.Dec(int64(sizeDel))
}
return err
}
// ForcePrune delete the content which distance is further than the given radius
func (p *ContentStorage) ForcePrune(radius *uint256.Int) error {
return p.deleteContentOutOfRadius(radius)
}

View file

@ -1,315 +0,0 @@
package history
import (
"fmt"
"math"
"os"
"testing"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
contentStorage "github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
)
const nodeDataDir = "./unit_test"
func clearNodeData() {
_ = os.Remove(fmt.Sprintf("%s/%s/%s", nodeDataDir, "history", sqliteName))
}
func genBytes(length int) []byte {
res := make([]byte, length)
for i := 0; i < length; i++ {
res[i] = byte(i)
}
return res
}
func newContentStorage(storageCapacityInMB uint64, nodeId enode.ID, nodeDataDir string) (*ContentStorage, error) {
db, err := NewDB(nodeDataDir, "history")
if err != nil {
return nil, err
}
hs, err := NewHistoryStorage(storage.PortalStorageConfig{
DB: db,
StorageCapacityMB: storageCapacityInMB,
NodeId: nodeId,
})
if err != nil {
return nil, err
}
return hs.(*ContentStorage), nil
}
func TestBasicStorage(t *testing.T) {
zeroNodeId := uint256.NewInt(0).Bytes32()
storage, err := newContentStorage(math.MaxUint32, zeroNodeId, nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
contentId := []byte("test")
content := []byte("value")
_, err = storage.Get(nil, contentId)
assert.Equal(t, contentStorage.ErrContentNotFound, err)
pt := storage.put(contentId, content)
assert.NoError(t, pt.Err())
val, err := storage.Get(nil, contentId)
assert.NoError(t, err)
assert.Equal(t, content, val)
count, err := storage.ContentCount()
assert.NoError(t, err)
assert.Equal(t, count, uint64(1))
size, err := storage.Size()
assert.NoError(t, err)
assert.True(t, size > 0)
unusedSize, err := storage.UnusedSize()
assert.NoError(t, err)
usedSize, err := storage.UsedSize()
assert.NoError(t, err)
assert.True(t, usedSize == size-unusedSize)
}
func TestDBSize(t *testing.T) {
zeroNodeId := uint256.NewInt(0).Bytes32()
storage, err := newContentStorage(math.MaxUint32, zeroNodeId, nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
numBytes := 10000
size1, err := storage.Size()
assert.NoError(t, err)
putResult := storage.put(uint256.NewInt(1).Bytes(), genBytes(numBytes))
assert.Nil(t, putResult.Err())
size2, err := storage.Size()
assert.NoError(t, err)
putResult = storage.put(uint256.NewInt(2).Bytes(), genBytes(numBytes))
assert.NoError(t, putResult.Err())
size3, err := storage.Size()
assert.NoError(t, err)
putResult = storage.put(uint256.NewInt(2).Bytes(), genBytes(numBytes))
assert.NoError(t, putResult.Err())
size4, err := storage.Size()
assert.NoError(t, err)
usedSize, err := storage.UsedSize()
assert.NoError(t, err)
assert.True(t, size2 > size1)
assert.True(t, size3 > size2)
assert.True(t, size4 == size3)
assert.True(t, usedSize == size4)
err = storage.del(uint256.NewInt(2).Bytes())
assert.NoError(t, err)
err = storage.del(uint256.NewInt(1).Bytes())
assert.NoError(t, err)
usedSize1, err := storage.UsedSize()
assert.NoError(t, err)
size5, err := storage.Size()
assert.NoError(t, err)
assert.True(t, size4 == size5)
assert.True(t, usedSize1 < size5)
err = storage.ReclaimSpace()
assert.NoError(t, err)
usedSize2, err := storage.UsedSize()
assert.NoError(t, err)
size6, err := storage.Size()
assert.NoError(t, err)
assert.Equal(t, size1, size6)
assert.Equal(t, usedSize2, size6)
}
func TestDBPruning(t *testing.T) {
storageCapacity := uint64(1)
zeroNodeId := uint256.NewInt(0).Bytes32()
storage, err := newContentStorage(storageCapacity, zeroNodeId, nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
furthestElement := uint256.NewInt(40)
secondFurthest := uint256.NewInt(30)
thirdFurthest := uint256.NewInt(20)
numBytes := 100_000
// test with private put method
pt1 := storage.put(uint256.NewInt(1).Bytes(), genBytes(numBytes))
assert.NoError(t, pt1.Err())
pt2 := storage.put(thirdFurthest.Bytes(), genBytes(numBytes))
assert.NoError(t, pt2.Err())
pt3 := storage.put(uint256.NewInt(3).Bytes(), genBytes(numBytes))
assert.NoError(t, pt3.Err())
pt4 := storage.put(uint256.NewInt(10).Bytes(), genBytes(numBytes))
assert.NoError(t, pt4.Err())
pt5 := storage.put(uint256.NewInt(5).Bytes(), genBytes(numBytes))
assert.NoError(t, pt5.Err())
pt6 := storage.put(uint256.NewInt(11).Bytes(), genBytes(numBytes))
assert.NoError(t, pt6.Err())
pt7 := storage.put(furthestElement.Bytes(), genBytes(40000))
assert.NoError(t, pt7.Err())
pt8 := storage.put(secondFurthest.Bytes(), genBytes(30000))
assert.NoError(t, pt8.Err())
pt9 := storage.put(uint256.NewInt(2).Bytes(), genBytes(numBytes*2))
assert.NoError(t, pt9.Err())
res, _ := storage.GetLargestDistance()
assert.Equal(t, res, uint256.NewInt(40))
pt10 := storage.put(uint256.NewInt(4).Bytes(), genBytes(132000))
assert.NoError(t, pt10.Err())
assert.False(t, pt1.Pruned())
assert.False(t, pt2.Pruned())
assert.False(t, pt3.Pruned())
assert.False(t, pt4.Pruned())
assert.False(t, pt5.Pruned())
assert.False(t, pt6.Pruned())
assert.False(t, pt7.Pruned())
assert.False(t, pt8.Pruned())
assert.False(t, pt9.Pruned())
assert.True(t, pt10.Pruned())
assert.Equal(t, pt10.PrunedCount(), 2)
usedSize, err := storage.UsedSize()
assert.NoError(t, err)
assert.True(t, usedSize < storage.storageCapacityInBytes)
_, err = storage.Get(nil, furthestElement.Bytes())
assert.Equal(t, contentStorage.ErrContentNotFound, err)
_, err = storage.Get(nil, secondFurthest.Bytes())
assert.Equal(t, contentStorage.ErrContentNotFound, err)
val, err := storage.Get(nil, thirdFurthest.Bytes())
assert.NoError(t, err)
assert.NotNil(t, val)
}
func TestGetLargestDistance(t *testing.T) {
storageCapacity := uint64(1)
zeroNodeId := uint256.NewInt(0).Bytes32()
storage, err := newContentStorage(storageCapacity, zeroNodeId, nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
furthestElement := uint256.NewInt(40)
secondFurthest := uint256.NewInt(30)
pt7 := storage.put(furthestElement.Bytes(), genBytes(2000))
assert.NoError(t, pt7.Err())
val, err := storage.Get(nil, furthestElement.Bytes())
assert.NoError(t, err)
assert.NotNil(t, val)
pt8 := storage.put(secondFurthest.Bytes(), genBytes(2000))
assert.NoError(t, pt8.Err())
res, err := storage.GetLargestDistance()
assert.NoError(t, err)
assert.Equal(t, furthestElement, res)
}
func TestSimpleForcePruning(t *testing.T) {
storageCapacity := uint64(100_000)
zeroNodeId := uint256.NewInt(0).Bytes32()
storage, err := newContentStorage(storageCapacity, zeroNodeId, nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
furthestElement := uint256.NewInt(40)
secondFurthest := uint256.NewInt(30)
third := uint256.NewInt(10)
pt1 := storage.put(furthestElement.Bytes(), genBytes(2000))
assert.NoError(t, pt1.Err())
pt2 := storage.put(secondFurthest.Bytes(), genBytes(2000))
assert.NoError(t, pt2.Err())
pt3 := storage.put(third.Bytes(), genBytes(2000))
assert.NoError(t, pt3.Err())
res, err := storage.GetLargestDistance()
assert.NoError(t, err)
assert.Equal(t, furthestElement, res)
err = storage.ForcePrune(uint256.NewInt(20))
assert.NoError(t, err)
_, err = storage.Get(nil, furthestElement.Bytes())
assert.Equal(t, contentStorage.ErrContentNotFound, err)
_, err = storage.Get(nil, secondFurthest.Bytes())
assert.Equal(t, contentStorage.ErrContentNotFound, err)
_, err = storage.Get(nil, third.Bytes())
assert.NoError(t, err)
}
func TestForcePruning(t *testing.T) {
const startCap = uint64(14_159_872)
const endCapacity = uint64(5000_000)
const amountOfItems = 10_000
maxUint256 := uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
nodeId := uint256.MustFromHex("0x30994892f3e4889d99deb5340050510d1842778acc7a7948adffa475fed51d6e").Bytes()
content := genBytes(1000)
storage, err := newContentStorage(startCap, enode.ID(nodeId), nodeDataDir)
assert.NoError(t, err)
defer clearNodeData()
defer storage.Close()
storage.storageCapacityInBytes = startCap
increment := uint256.NewInt(0).Div(maxUint256, uint256.NewInt(amountOfItems))
remainder := uint256.NewInt(0).Mod(maxUint256, uint256.NewInt(amountOfItems))
id := uint256.NewInt(0)
putCount := 0
// id < maxUint256 - remainder
for id.Cmp(uint256.NewInt(0).Sub(maxUint256, remainder)) == -1 {
res := storage.put(id.Bytes(), content)
assert.NoError(t, res.Err())
id = id.Add(id, increment)
putCount++
}
storage.storageCapacityInBytes = endCapacity
oldDistance, err := storage.GetLargestDistance()
assert.NoError(t, err)
newDistance, err := storage.EstimateNewRadius(oldDistance)
assert.NoError(t, err)
assert.NotEqual(t, oldDistance.Cmp(newDistance), -1)
err = storage.ForcePrune(newDistance)
assert.NoError(t, err)
var total int64
err = storage.sqliteDB.QueryRow("SELECT count(*) FROM kvstore where greater(xor(key, (?1)), (?2)) = 1", storage.nodeId[:], newDistance.Bytes()).Scan(&total)
assert.NoError(t, err)
assert.Equal(t, int64(0), total)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,33 +0,0 @@
# block number: 15539558
execution_block_header: 0xcdf9ed89b0c43cda17398dc4da9cfc505e5ccd19f7c39e3b43474180f1051e01
beacon_block_body_proof:
- 0x72e99d023990f3228488ccd391a6916a65715958ae7e3e3476e6ef3b24a14799
- 0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b
- 0xa06acb149ac136c27ab9c4b30624f861d4ead5bb9fa725993767ccca90dcfd4d
- 0x44f09e14b80476731f84bf7b3c85bce0259e8f2a3abffb9e0ac8f28d055b2a36
- 0x881452718c4c085088bdee30b957b2a3d943caeacc39c390632af90a65e369bd
- 0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b
- 0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71
- 0x982c6a980438c3df73968f2c372029add74aa2e0b1b3ccfebb1d9cac7660c61d
beacon_block_body_root: 0x8c7dbf6c53814a2d793a9af8e7b221ff69b5e3b1469ccad397b5561c64886143
beacon_block_header_proof:
- 0x0000000000000000000000000000000000000000000000000000000000000000
- 0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b
- 0xc694a9f6d954c949cf25b85c4f686d267eaafca8dc86afda41f9434a3a30048e
beacon_block_header_root: 0x4b72c935466fa0857a7320c4fee7c99f892adb686ed7a66aa38f26f4ef3c2f21
historical_roots_proof:
- 0xda1b49bf9408d2e8bf5dd5cba08917973b6948abb17ef6a9d602d9e9575cbf7f
- 0xb9ac7a390e27da1159903a5345e4f4a094fb66509e2899d877e184caa49d7b42
- 0x8577093326bf889b29261d95b02728e73acf5e0bc2bcc9eb90d1c7c41979ae72
- 0xee3d2526ce774ee787b4a6eab4aa9953ae683c90aa3f21d07a8877b43e3627fb
- 0xe2288f273c4be422f7407254408a43ca92b2cc8fe680ea4d14f8e3174c98f527
- 0x63c5ce4b0d6dd285a77dffcb4c944e57f56ea565170a93a45ae9fc64bad5ce0a
- 0x7e1e6e6818e3626663fb02bafe8031ff7896ae87fa9896d7028a2b9e8b99354b
- 0x1da1e4d70d91cf40475cd0b9679de35cbc11fa5c5c2c410935c93fd1ef3b4697
- 0xeb164971bc2c332c0174b7f2bf25f8719bcc95048d2cc3235a789cec1adbb077
- 0x967538697e44dd1c12d2c5f92e9852a09d23063220e0f3e8a1acfbc2eda0afc0
- 0xd7407ca72fc746f5087c4bb5d0754bd9df0af7ab7dbc3be1705a1c1e3cd5b8f8
- 0xf433674c06fd0a3a9cb9615f9c0a37348b106905a960249d17ffd23c20192704
- 0xc1eb114f29abc95642dc93f8d5f38fd248a67b4f8d603ff4157dcb1e8ee09706
- 0x83b655426b47df48bb29e337ea37bcfe2bd9e33a13668ba3bc27919d421a40a6
slot: 4702208

File diff suppressed because one or more lines are too long

View file

@ -1,18 +0,0 @@
{
"0": "0xf90214a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000850400000000808213888080a011bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82faa00000000000000000000000000000000000000000000000000000000000000000880000000000000042",
"1": "0xf90211a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479405a56e2d52c817161883f50c441c3228cfe54d9fa0d67e4d450343046425ae4271474353857ab860dbc0a1dde64b41b5cd3a532bf3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008503ff80000001821388808455ba422499476574682f76312e302e302f6c696e75782f676f312e342e32a0969b900de27b6ac6a67742365dd65f55a0526c41fd18e1b16f1a1215c2e66f5988539bd4979fef1ec4",
"2": "0xf90218a088e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794dd2f1e6e498202e86d8f5442af596580a4f03c2ca04943d941637411107494da9ec8bc04359d731bfd08b72b4d0edcbd4cd2ecb341a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008503ff00100002821388808455ba4241a0476574682f76312e302e302d30636463373634372f6c696e75782f676f312e34a02f0790c5aa31ab94195e1f6443d645af5b75c46c04fbf9911711198a0ce8fdda88b853fa261a86aa9e",
"200000": "0xf90213a07f27ffbccbbf32b53697930c508137e451e8de080231008d945c6e3ed631b74aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347941dcb8d1f0fcc8cbc8c2d76528e877f915e299fbea0632964149a2056cb246ccee21838d139516578712f13b2a7cbf0086969d0f4aba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008605ae701ab58e83030d40832fefd8808455ee029596d583010102844765746885676f312e35856c696e7578a0f9d7884dab1938bd8100a4564a949256aedb8936ad3f72f48eaa679269560a65883ec79c2d077b8db2",
"669051": "0xf90217a092bccf7a38604c5441dffc5eb5a5ca295b3fbb7ff01cc92fb3b48f0d456e732ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794f8b483dba2c3b7176a3da549ad41a48bb3121069a08a779b9d52800c3f0fc2ec4f8388dd56e1fcf4685126466bc1a9832ab2ddf612a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860717d1b1cd0e830a357b832fefd88084566930a39ad983010203844765746887676f312e342e328777696e646f7773a0daa40d4b72000209b43526ada798b90b98f9cd6e4cdc5bebbad690208aa1728788e6b9441a5df2f6ad",
"15537393": "0xf9021ba02b3ea3cd4befcab070812443affb08bf17a91ce382c714a536ca3cacab82278ba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794829bd824b016326a401d083b33d092293333a830a04919dafa6ac8becfbbd0c2808f6c9511a057c21e42839caff5dfb6d3ef514951a0dd5eec02b019ff76e359b09bfa19395a2a0e97bc01e70d8d5491e640167c96a8a0baa842cfd552321a9c2450576126311e071680a1258032219c6490b663c1dab8b90100000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000200000000000000000008000000000040000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000000010020000000000000000000000000000000000020000000200000000200000000000000000000000000000000000000000400000000000000000000000008727472e1db3626a83ed14f18401c9c3808401c9a205846322c96292e4b883e5bda9e7a59ee4bb99e9b1bc460021a04cbec03dddd4b939730a7fe6048729604d4266e82426d472a2b2024f3cc4043f8862a3ee77461d4fc9850a1a4e5f06",
"1000001": "0xf90217a08e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a07dd4aabb93795feba9866821c0c7d6a992eda7fbdd412ea0f715059f9654ef23a0c61c50a0a2800ddc5e9984af4e6668de96aee1584179b3141f458ffa7d4ecec6a0b873ddefdb56d448343d13b188241a4919b2de10cccea2ea573acf8dbc839befb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4bbd735f830f4241832fefd88252088456bfb41a98d783010303844765746887676f312e352e31856c696e7578a0d5332614a151dd917b84fc5ff62580d7099edb7c37e0ac843d873de978d50352889112b8c2b377fbe8",
"1000002": "0xf90217a0cb5cab7266694daa0d28cbf40496c08dd30bf732c41e0455e7ad389c10d79f4fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479495581ea0c5b362933f3523138f54d51eae817211a0643430d1afc3f02ce5249e4ba5979fb8601b1907a5923a4a74d36d66321a27e5a0dbdf7457111e50e435853974d5412c2151fde6e3c2e3f5aecc253aa4cb21fce2a097097902b6b4d6b695ef16b923e33b8780d95cf4bd54540ac450deb019d07647b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b69de53fcb1830f4242832fefd882f6188456bfb42e98d783010303844765746887676f312e352e31856c696e7578a0a01f9d00ac510a726f883459834e30cfe085f47b04e22f72207f5a9e9d652ca6881c080c4ec6f2553b",
"1000003": "0xf90217a095c3a05973fec7bf98f1131a72e607b4eba171d0576571cf83ee7162bbcdb7d9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0651bd0c6ad32da06a732db9797ced42e01ca607b3d049832486ce0f98b2ac517a0eef5869831e31e8e92a812916adfe27b2902f7b9e10246e38beec7df23e818dca06eaba9039ef6c055a3796d90f2ab1eeb86cfb4f9fec56c1eb097188950f35ec5b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b8fc830830f4243832fefd88252088456bfb43098d783010303844765746887676f312e352e31856c696e7578a00b52aa3b442bc0e85c53a9708ee14a2f7f9fdf87b4ed52dced8fcdc0ffbd0e2e88b69d3fa0a7107603",
"1000004": "0xf90217a0ed08bd684ca0167101054b8e8baaef5b28663a9936e9347424a810e493250d25a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a09bb6ebcf9d87354039fdcdf1ebbd8aae154155e57fcb38a371cdcfec533ead51a04d115b466f31be0c927a80eafa9e3e04ba612fa3578eb2b7bf2b284d297d9cf9a00a60403314e4be4fcea22907e4c57b3a887aae6a5b4490298b0cec4837c6693bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b69de265737830f4244832fefd882a4108456bfb44498d783010303844765746887676f312e352e31856c696e7578a038bd108c803e477efe1053b5b82875150f221aa95ea38623da13dad53aa2634688cfedf9cf294baade",
"1000005": "0xf90217a05c2689d27bfeded9faa0d52e7301bb425e0758ee2b550b852557776e5453ed48a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0eb6e181798a9f28e1549d5818ae9e2b89f2d12e80d52e43fb6afbe0d876d2755a0678a5351a2bc45773aed24446d87ef7a0a67c0e6005a4c945c1ab9f4124b8baea067766475549b952e6ab6c973dbddbab8cf017cfcc43e56c08b7ee4494b05a053b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b621d01830f4245832fefd88252088456bfb44c98d783010303844765746887676f312e352e31856c696e7578a0577dfcb7885b10af0b1f3c9ce059fe10f1f4f25b4ec6fc33fc916b861dc7e11d8871f6405556868e15",
"1000006": "0xf90215a0de9808464da8c76074e77ceb53917fbb58ef8057472c9b24f1332cc293215b91a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479463a9975ba31b0b9626b34300f7f627147df1f526a05f15197e6511710c05e2474b0f2cc9b24edae8d1221bf66f2c348657a47b1dc8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6cb8cb8a44830f4246832fefd8808456bfb44e98d783010400844765746887676f312e352e31856c696e7578a082e43f95bfaf9aa2e9d106f34bfc1bb0e127c51cf476dd224e81e304ceaab0fc88863127de92b0e7e9",
"1000007": "0xf9021aa03962187c363ce329fd05a41b74017a0a693f0cc5383eb790afad37dcfd1a4b3ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794f8b483dba2c3b7176a3da549ad41a48bb3121069a03d30cf33487586dc69cc29227e031519b9196b0f6f62f5432d56a949eaf41deba0334799df0c6e58fa0fc8a12065faa9669d81b41befc35de295e1688d24a9e4eda0c1f5d246ba496e41b3a47ae8da0e8c23381c3ee5b09128805c7a4630a2651394b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b3471d3830f4247832fefd883019a288456bfb47d9ad983010302844765746887676f312e342e328777696e646f7773a0181d92d747842e835f2749c6c270a140bf19b2145c210e901f7b70a2b988259888b5afeac367d84d68",
"1000008": "0xf90217a07d4fbba665d462a39a06d98e2c57df0d5e34fc7660a064e44617e20143e3c78ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479468795c4aa09d6f4ed3e5deddf8c2ad3049a601daa08da9a5b0d31d90c6aee4d3a29f80f026425ab967bb50b3a75b363ffde1c9c882a0b23c3d805f1e1002471aa5aff5a4fa60795c163ca288dc77c3b8870ddba989e7a05cbfe86e7c01bf19215d9a6398665e84bf38b6c76ccc87107df469e8827c6962b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6cb89dd961830f4248832fefd88252088456bfb48098d783010400844765746887676f312e352e31856c696e7578a0a4fe220f13171d30b40f76b0f891310923f742e2370318e50eaf3324720bba0588b23301ed5b0c8e67",
"1000009": "0xf90215a05d1a17185e3b28bb6d6e6bacb37ea2164f4167c9738a23f802a629af1bdf17d9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479452dc504a422f0e2a9e7632a34a50f1a82f8224c7a0c8566a988385f3998e4704d464b4cff65a91a0fa4a22de4e8335e536eaadd1a1a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b06c6a6830f4249832fefd8808456bfb48e98d783010303844765746887676f312e352e31856c696e7578a0a746fd5b8dc7c8f8771e6e5a9d90774152421842fd66a353ecfa8013f512803a8833eb6f003aace9d9",
"1000010": "0xf90218a00409be8253ad6ac0eb2056bc94194c6ccb83c74f4292c40c82e2dc8203bdc759a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0afbf9bfd23008e8df44a83bb51ade45b993b3253fbce69cf7cec5d628eca6d45a0a7120e4bd136c0b6bdb0fa4990649f8c34d10d180dbd5ad6d03502ae92d32308a0d78aa953fedc7f7c112b2686d0b2b7e37eba716dd1f5d74ef3c8a37005f35215b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000004000000000000000000040000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000860b69dd9d66ce830f424a832fefd88303a68c8456bfb4e398d783010303844765746887676f312e352e31856c696e7578a0e962efb883f91286e4fc6fd12989a70f24c174bd087f472528137c4134af0a1a88e857c5acc15dd827"
}

View file

@ -1,42 +0,0 @@
{
"1000001": {
"content_key": "0x00cb5cab7266694daa0d28cbf40496c08dd30bf732c41e0455e7ad389c10d79f4f",
"value": "0x0800000022020000f90217a08e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a07dd4aabb93795feba9866821c0c7d6a992eda7fbdd412ea0f715059f9654ef23a0c61c50a0a2800ddc5e9984af4e6668de96aee1584179b3141f458ffa7d4ecec6a0b873ddefdb56d448343d13b188241a4919b2de10cccea2ea573acf8dbc839befb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4bbd735f830f4241832fefd88252088456bfb41a98d783010303844765746887676f312e352e31856c696e7578a0d5332614a151dd917b84fc5ff62580d7099edb7c37e0ac843d873de978d50352889112b8c2b377fbe801c971eaaa41600563000000000000000000000000000000000000000000000000629f9dbe275316ef21073133b8ecec062a44e20201be7b24a22c56db91df336f0c71aaaec1b3526027a54b15387ef014fcd18bb46e90e05657b46418fd326e785392c40ec6d38f000042798fee52ed833ff376b1d5a95dc7c2356dc8d8d02e30b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000002": {
"content_key": "0x0095c3a05973fec7bf98f1131a72e607b4eba171d0576571cf83ee7162bbcdb7d9",
"value": "0x0800000022020000f90217a0cb5cab7266694daa0d28cbf40496c08dd30bf732c41e0455e7ad389c10d79f4fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479495581ea0c5b362933f3523138f54d51eae817211a0643430d1afc3f02ce5249e4ba5979fb8601b1907a5923a4a74d36d66321a27e5a0dbdf7457111e50e435853974d5412c2151fde6e3c2e3f5aecc253aa4cb21fce2a097097902b6b4d6b695ef16b923e33b8780d95cf4bd54540ac450deb019d07647b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b69de53fcb1830f4242832fefd882f6188456bfb42e98d783010303844765746887676f312e352e31856c696e7578a0a01f9d00ac510a726f883459834e30cfe085f47b04e22f72207f5a9e9d652ca6881c080c4ec6f2553b017a6e3e89ab6b056300000000000000000000000000000000000000000000000030a7f33265c53f74e978e394ce395aaf1247e8d878ad7924c730beedf21f997ef4cb3507d87cf63a4e94fd8d559a5aa29598a0fbc997b3d7abb68cb9239d83c35392c40ec6d38f000042798fee52ed833ff376b1d5a95dc7c2356dc8d8d02e30b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000003": {
"content_key": "0x00ed08bd684ca0167101054b8e8baaef5b28663a9936e9347424a810e493250d25",
"value": "0x0800000022020000f90217a095c3a05973fec7bf98f1131a72e607b4eba171d0576571cf83ee7162bbcdb7d9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0651bd0c6ad32da06a732db9797ced42e01ca607b3d049832486ce0f98b2ac517a0eef5869831e31e8e92a812916adfe27b2902f7b9e10246e38beec7df23e818dca06eaba9039ef6c055a3796d90f2ab1eeb86cfb4f9fec56c1eb097188950f35ec5b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b8fc830830f4243832fefd88252088456bfb43098d783010303844765746887676f312e352e31856c696e7578a00b52aa3b442bc0e85c53a9708ee14a2f7f9fdf87b4ed52dced8fcdc0ffbd0e2e88b69d3fa0a710760301aa36ced416770563000000000000000000000000000000000000000000000000cf640afdac3593c1cf722ad68a5a536688c110d5d862390cedd25f42ce03faaef4cb3507d87cf63a4e94fd8d559a5aa29598a0fbc997b3d7abb68cb9239d83c35392c40ec6d38f000042798fee52ed833ff376b1d5a95dc7c2356dc8d8d02e30b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000004": {
"content_key": "0x005c2689d27bfeded9faa0d52e7301bb425e0758ee2b550b852557776e5453ed48",
"value": "0x0800000022020000f90217a0ed08bd684ca0167101054b8e8baaef5b28663a9936e9347424a810e493250d25a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a09bb6ebcf9d87354039fdcdf1ebbd8aae154155e57fcb38a371cdcfec533ead51a04d115b466f31be0c927a80eafa9e3e04ba612fa3578eb2b7bf2b284d297d9cf9a00a60403314e4be4fcea22907e4c57b3a887aae6a5b4490298b0cec4837c6693bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b69de265737830f4244832fefd882a4108456bfb44498d783010303844765746887676f312e352e31856c696e7578a038bd108c803e477efe1053b5b82875150f221aa95ea38623da13dad53aa2634688cfedf9cf294baade01e18df4b2808205630000000000000000000000000000000000000000000000003adbb422354ef244812ab70e2d9bb1c48461a93df635a1423d36f49d67790deb1dfb82c1fc4a6632d8d41f0e6ba37172d240fb052f1f1c2bfc9a5a7bf03e3b059349d18c2f925a07d81acea8e46e9e12746357cdd21ffcd42bae5957404ee757b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000005": {
"content_key": "0x00de9808464da8c76074e77ceb53917fbb58ef8057472c9b24f1332cc293215b91",
"value": "0x0800000022020000f90217a05c2689d27bfeded9faa0d52e7301bb425e0758ee2b550b852557776e5453ed48a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0eb6e181798a9f28e1549d5818ae9e2b89f2d12e80d52e43fb6afbe0d876d2755a0678a5351a2bc45773aed24446d87ef7a0a67c0e6005a4c945c1ab9f4124b8baea067766475549b952e6ab6c973dbddbab8cf017cfcc43e56c08b7ee4494b05a053b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b621d01830f4245832fefd88252088456bfb44c98d783010303844765746887676f312e352e31856c696e7578a0577dfcb7885b10af0b1f3c9ce059fe10f1f4f25b4ec6fc33fc916b861dc7e11d8871f6405556868e1501e2aa56feeb8d0563000000000000000000000000000000000000000000000000b158b0a4fea8d2f5094a4e4263dde3478263805d0a5674b23ad9b2fa42d7deca1dfb82c1fc4a6632d8d41f0e6ba37172d240fb052f1f1c2bfc9a5a7bf03e3b059349d18c2f925a07d81acea8e46e9e12746357cdd21ffcd42bae5957404ee757b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000006": {
"content_key": "0x003962187c363ce329fd05a41b74017a0a693f0cc5383eb790afad37dcfd1a4b3c",
"value": "0x0800000020020000f90215a0de9808464da8c76074e77ceb53917fbb58ef8057472c9b24f1332cc293215b91a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479463a9975ba31b0b9626b34300f7f627147df1f526a05f15197e6511710c05e2474b0f2cc9b24edae8d1221bf66f2c348657a47b1dc8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6cb8cb8a44830f4246832fefd8808456bfb44e98d783010400844765746887676f312e352e31856c696e7578a082e43f95bfaf9aa2e9d106f34bfc1bb0e127c51cf476dd224e81e304ceaab0fc88863127de92b0e7e901263522b7589905630000000000000000000000000000000000000000000000006b23456915f5fc5be0c705797ba9f3d47c099c26b7593875642dfd899fc36a01145d3c2df0e829c55d10be53a01706ff92e445c3870b819d0333feadda3edf8c9349d18c2f925a07d81acea8e46e9e12746357cdd21ffcd42bae5957404ee757b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000007": {
"content_key": "0x007d4fbba665d462a39a06d98e2c57df0d5e34fc7660a064e44617e20143e3c78c",
"value": "0x0800000025020000f9021aa03962187c363ce329fd05a41b74017a0a693f0cc5383eb790afad37dcfd1a4b3ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794f8b483dba2c3b7176a3da549ad41a48bb3121069a03d30cf33487586dc69cc29227e031519b9196b0f6f62f5432d56a949eaf41deba0334799df0c6e58fa0fc8a12065faa9669d81b41befc35de295e1688d24a9e4eda0c1f5d246ba496e41b3a47ae8da0e8c23381c3ee5b09128805c7a4630a2651394b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b3471d3830f4247832fefd883019a288456bfb47d9ad983010302844765746887676f312e342e328777696e646f7773a0181d92d747842e835f2749c6c270a140bf19b2145c210e901f7b70a2b988259888b5afeac367d84d6801f9a65602c4a4056300000000000000000000000000000000000000000000000061410e3978de33e61cf96b22d14763e4b5c5a6ad901d89ac0cd3323dc406b918145d3c2df0e829c55d10be53a01706ff92e445c3870b819d0333feadda3edf8c9349d18c2f925a07d81acea8e46e9e12746357cdd21ffcd42bae5957404ee757b704e9ee8e4d712920a18fd4e8833a7979a14e5b972d4b27958dcfa5187e3aa14d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000008": {
"content_key": "0x005d1a17185e3b28bb6d6e6bacb37ea2164f4167c9738a23f802a629af1bdf17d9",
"value": "0x0800000022020000f90217a07d4fbba665d462a39a06d98e2c57df0d5e34fc7660a064e44617e20143e3c78ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479468795c4aa09d6f4ed3e5deddf8c2ad3049a601daa08da9a5b0d31d90c6aee4d3a29f80f026425ab967bb50b3a75b363ffde1c9c882a0b23c3d805f1e1002471aa5aff5a4fa60795c163ca288dc77c3b8870ddba989e7a05cbfe86e7c01bf19215d9a6398665e84bf38b6c76ccc87107df469e8827c6962b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6cb89dd961830f4248832fefd88252088456bfb48098d783010400844765746887676f312e352e31856c696e7578a0a4fe220f13171d30b40f76b0f891310923f742e2370318e50eaf3324720bba0588b23301ed5b0c8e67015a80f4ba30b0056300000000000000000000000000000000000000000000000052ef50f0cc153776ad48d638b1ac79178f6452299d4c3e6be8014a03a27b1e60d1fd24ef33b3f7c8d15cd0d92ce0aa9d84d3492d0b24e5e65e2f5e5567e858086d800f67f5331ee2e511dc20e169c644b3df0f4c6b7c1717fc29d4844050b74044b506bf91edd14825aaec4f36fc5ad97b9eed9773aa2df15f80dff21eb668e24d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000009": {
"content_key": "0x000409be8253ad6ac0eb2056bc94194c6ccb83c74f4292c40c82e2dc8203bdc759",
"value": "0x0800000020020000f90215a05d1a17185e3b28bb6d6e6bacb37ea2164f4167c9738a23f802a629af1bdf17d9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479452dc504a422f0e2a9e7632a34a50f1a82f8224c7a0c8566a988385f3998e4704d464b4cff65a91a0fa4a22de4e8335e536eaadd1a1a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000860b6b4b06c6a6830f4249832fefd8808456bfb48e98d783010303844765746887676f312e352e31856c696e7578a0a746fd5b8dc7c8f8771e6e5a9d90774152421842fd66a353ecfa8013f512803a8833eb6f003aace9d9010047fb059cbb0563000000000000000000000000000000000000000000000000e17b024ddb03e64d58a1797016fe2de5a78b243b84778fe8dba4480515c0bb01d1fd24ef33b3f7c8d15cd0d92ce0aa9d84d3492d0b24e5e65e2f5e5567e858086d800f67f5331ee2e511dc20e169c644b3df0f4c6b7c1717fc29d4844050b74044b506bf91edd14825aaec4f36fc5ad97b9eed9773aa2df15f80dff21eb668e24d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
},
"1000010": {
"content_key": "0x006251d65b8a8668efabe2f89c96a5b6332d83b3bbe585089ea6b2ab9b6754f5e9",
"value": "0x0800000023020000f90218a00409be8253ad6ac0eb2056bc94194c6ccb83c74f4292c40c82e2dc8203bdc759a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0afbf9bfd23008e8df44a83bb51ade45b993b3253fbce69cf7cec5d628eca6d45a0a7120e4bd136c0b6bdb0fa4990649f8c34d10d180dbd5ad6d03502ae92d32308a0d78aa953fedc7f7c112b2686d0b2b7e37eba716dd1f5d74ef3c8a37005f35215b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000004000000000000000000040000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000860b69dd9d66ce830f424a832fefd88303a68c8456bfb4e398d783010303844765746887676f312e352e31856c696e7578a0e962efb883f91286e4fc6fd12989a70f24c174bd087f472528137c4134af0a1a88e857c5acc15dd82701cead98e305c70563000000000000000000000000000000000000000000000000be1b4a7a57f5316eea09c5e3e349141c46c1cb43664a815d28644cd74f282ca122360456d89447c0d586a8f5490922ea86b20e056879d64d87d104c14c0e594a6d800f67f5331ee2e511dc20e169c644b3df0f4c6b7c1717fc29d4844050b74044b506bf91edd14825aaec4f36fc5ad97b9eed9773aa2df15f80dff21eb668e24d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,244 +0,0 @@
package history
import (
"errors"
ssz "github.com/ferranbt/fastssz"
)
// note: We changed the generated file since fastssz issues which can't be passed by the CI, so we commented the go:generate line
///go:generate sszgen --path types.go --exclude-objs BlockHeaderProof,PortalReceipts
type BlockHeaderProofType uint8
const (
none BlockHeaderProofType = 0
accumulatorProof BlockHeaderProofType = 1
)
type HeaderRecord struct {
BlockHash []byte `ssz-size:"32"`
TotalDifficulty []byte `ssz-size:"32"`
}
type EpochAccumulator struct {
HeaderRecords [][]byte `ssz-size:"8192,64"`
}
type BlockBodyLegacy struct {
Transactions [][]byte `ssz-max:"16384,16777216"`
Uncles []byte `ssz-max:"131072"`
}
type PortalBlockBodyShanghai struct {
Transactions [][]byte `ssz-max:"16384,16777216"`
Uncles []byte `ssz-max:"131072"`
Withdrawals [][]byte `ssz-max:"16,192"`
}
type BlockHeaderWithProof struct {
Header []byte `ssz-max:"8192"`
Proof *BlockHeaderProof `ssz-max:"512"`
}
type SSZProof struct {
Leaf []byte `ssz-size:"32"`
Witnesses [][]byte `ssz-max:"65536,32" ssz-size:"?,32"`
}
type MasterAccumulator struct {
HistoricalEpochs [][]byte `ssz-max:"1897,32" ssz-size:"?,32"`
}
// BlockHeaderProof is a ssz union type
// Union[None, AccumulatorProof]
type BlockHeaderProof struct {
Selector BlockHeaderProofType
Proof [][]byte `ssz-size:"15,32"`
}
func (p *BlockHeaderProof) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(p)
}
func (p *BlockHeaderProof) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
dst = append(dst, byte(p.Selector))
if p.Selector != none {
if len(p.Proof) != 15 {
err = ssz.ErrBytesLengthFn("proofs size should be", len(p.Proof), 15)
return
}
for _, item := range p.Proof {
if len(item) != 32 {
err = ssz.ErrBytesLengthFn("single proof size should be", len(item), 32)
return
}
dst = append(dst, item...)
}
}
return
}
func (p *BlockHeaderProof) UnmarshalSSZ(buf []byte) (err error) {
p.Selector = BlockHeaderProofType(buf[0])
if p.Selector == none {
return
}
if p.Selector != accumulatorProof {
return errors.New("unknown accumulatorProofType, shoud be 0x00 or 0x01")
}
proofBytes := buf[1:]
if len(proofBytes) != 32*15 {
return ssz.ErrBytesLengthFn("AccumulatorProof", len(proofBytes), 32*15)
}
proof := make([][]byte, 15)
for i := 0; i < 15; i++ {
proof[i] = proofBytes[i*32 : (i+1)*32]
}
p.Proof = proof
return
}
func (p *BlockHeaderProof) SizeSSZ() (size int) {
size = 0
// Field (0) 'Selector'
size += 1
if p.Selector == none {
return size
}
// Field (1) 'Proof'
size += 15 * 32
return size
}
func (p *BlockHeaderProof) HashTreeRootWith(_ ssz.HashWalker) (err error) {
panic("implement me")
}
type PortalReceipts struct {
Receipts [][]byte `ssz-max:"16384,134217728"`
}
// MarshalSSZ ssz marshals the PortalReceipts object
func (p *PortalReceipts) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(p)
}
// MarshalSSZTo ssz marshals the PortalReceipts object to a target array
func (p *PortalReceipts) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'Receipts'
if size := len(p.Receipts); size > 16384 {
err = ssz.ErrListTooBigFn("PortalReceipts.Receipts", size, 16384)
return
}
{
offset := 4 * len(p.Receipts)
for ii := 0; ii < len(p.Receipts); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(p.Receipts[ii])
}
}
for ii := 0; ii < len(p.Receipts); ii++ {
if size := len(p.Receipts[ii]); size > 134217728 {
err = ssz.ErrBytesLengthFn("PortalReceipts.Receipts[ii]", size, 134217728)
return
}
dst = append(dst, p.Receipts[ii]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the PortalReceipts object
func (p *PortalReceipts) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 4 {
return ssz.ErrSize
}
// Field (0) 'Receipts'
{
num, err := ssz.DecodeDynamicLength(buf, 16384)
if err != nil {
return err
}
p.Receipts = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 134217728 {
return ssz.ErrBytesLength
}
if cap(p.Receipts[indx]) == 0 {
p.Receipts[indx] = make([]byte, 0, len(buf))
}
p.Receipts[indx] = append(p.Receipts[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the PortalReceipts object
func (p *PortalReceipts) SizeSSZ() (size int) {
size = 0
// Field (0) 'Receipts'
for ii := 0; ii < len(p.Receipts); ii++ {
size += 4
size += len(p.Receipts[ii])
}
return
}
// HashTreeRoot ssz hashes the PortalReceipts object
func (p *PortalReceipts) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(p)
}
// HashTreeRootWith ssz hashes the PortalReceipts object with a hasher
func (p *PortalReceipts) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Receipts'
{
subIndx := hh.Index()
num := uint64(len(p.Receipts))
if num > 16384 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range p.Receipts {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 134217728 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (134217728+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 16384)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the PortalReceipts object
func (p *PortalReceipts) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(p)
}

File diff suppressed because it is too large Load diff

View file

@ -1,95 +0,0 @@
package history
import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/holiman/uint256"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/view"
"github.com/stretchr/testify/require"
)
// testcases from https://github.com/ethereum/portal-network-specs/blob/master/content-keys-test-vectors.md
func TestContentKey(t *testing.T) {
testCases := []struct {
name string
hash string
contentKey string
contentIdHex string
contentIdU256 string
selector ContentType
}{
{
name: "block header key",
hash: "d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentKey: "00d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentIdHex: "3e86b3767b57402ea72e369ae0496ce47cc15be685bec3b4726b9f316e3895fe",
contentIdU256: "28281392725701906550238743427348001871342819822834514257505083923073246729726",
selector: BlockHeaderType,
},
{
name: "block body key",
hash: "d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentKey: "01d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentIdHex: "ebe414854629d60c58ddd5bf60fd72e41760a5f7a463fdcb169f13ee4a26786b",
contentIdU256: "106696502175825986237944249828698290888857178633945273402044845898673345165419",
selector: BlockBodyType,
},
{
name: "receipt key",
hash: "d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentKey: "02d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d",
contentIdHex: "a888f4aafe9109d495ac4d4774a6277c1ada42035e3da5e10a04cc93247c04a4",
contentIdU256: "76230538398907151249589044529104962263309222250374376758768131420767496438948",
selector: ReceiptsType,
},
}
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
hashByte, err := hex.DecodeString(c.hash)
require.NoError(t, err)
contentKey := newContentKey(c.selector, hashByte).encode()
hexKey := hex.EncodeToString(contentKey)
require.Equal(t, hexKey, c.contentKey)
contentId := ContentId(contentKey)
require.Equal(t, c.contentIdHex, hex.EncodeToString(contentId))
bigNum := big.NewInt(0).SetBytes(contentId)
u256Format, isOverflow := uint256.FromBig(bigNum)
require.False(t, isOverflow)
u256Str := fmt.Sprint(u256Format)
require.Equal(t, u256Str, c.contentIdU256)
})
}
}
func TestBlockNumber(t *testing.T) {
blockNumber := 12345678
contentKey := "0x034e61bc0000000000"
contentId := "0x2113990747a85ab39785d21342fa5db1f68acc0011605c0c73f68fc331643dcf"
contentIdU256 := "14960950260935695396511307566164035182676768442501235074589175304147024756175"
key := view.Uint64View(blockNumber)
var buf bytes.Buffer
err := key.Serialize(codec.NewEncodingWriter(&buf))
require.NoError(t, err)
keyData := []byte{byte(BlockHeaderNumberType)}
keyData = append(keyData, buf.Bytes()...)
require.Equal(t, hexutil.MustDecode(contentKey), keyData)
contentIdData := ContentId(keyData)
require.Equal(t, contentId, hexutil.Encode(contentIdData))
bigNum := big.NewInt(0).SetBytes(contentIdData)
u256Format, isOverflow := uint256.FromBig(bigNum)
require.False(t, isOverflow)
u256Str := fmt.Sprint(u256Format)
require.Equal(t, u256Str, contentIdU256)
}

View file

@ -1,198 +0,0 @@
package history
import (
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
"github.com/protolambda/ztyp/view"
)
const beaconBlockBodyProofLen = 8
type BeaconBlockBodyProof [beaconBlockBodyProofLen]common.Root
func (b *BeaconBlockBodyProof) Deserialize(dr *codec.DecodingReader) error {
roots := b[:]
return tree.ReadRoots(dr, &roots, beaconBlockBodyProofLen)
}
func (b *BeaconBlockBodyProof) Serialize(w *codec.EncodingWriter) error {
return tree.WriteRoots(w, b[:])
}
func (b BeaconBlockBodyProof) ByteLength() (out uint64) {
return beaconBlockBodyProofLen * 32
}
func (b BeaconBlockBodyProof) FixedLength() uint64 {
return beaconBlockBodyProofLen * 32
}
func (b *BeaconBlockBodyProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.ComplexVectorHTR(func(i uint64) tree.HTR {
if i < beaconBlockBodyProofLen {
return &b[i]
}
return nil
}, beaconBlockBodyProofLen)
}
const beaconBlockHeaderProofLen = 3
type BeaconBlockHeaderProof [beaconBlockHeaderProofLen]common.Root
func (b *BeaconBlockHeaderProof) Deserialize(dr *codec.DecodingReader) error {
roots := b[:]
return tree.ReadRoots(dr, &roots, beaconBlockHeaderProofLen)
}
func (b *BeaconBlockHeaderProof) Serialize(w *codec.EncodingWriter) error {
return tree.WriteRoots(w, b[:])
}
func (b BeaconBlockHeaderProof) ByteLength() (out uint64) {
return beaconBlockHeaderProofLen * 32
}
func (b BeaconBlockHeaderProof) FixedLength() uint64 {
return beaconBlockHeaderProofLen * 32
}
func (b *BeaconBlockHeaderProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.ComplexVectorHTR(func(i uint64) tree.HTR {
if i < beaconBlockHeaderProofLen {
return &b[i]
}
return nil
}, beaconBlockHeaderProofLen)
}
const historicalRootsProofLen = 14
type HistoricalRootsProof [historicalRootsProofLen]common.Root
func (b *HistoricalRootsProof) Deserialize(dr *codec.DecodingReader) error {
roots := b[:]
return tree.ReadRoots(dr, &roots, historicalRootsProofLen)
}
func (b *HistoricalRootsProof) Serialize(w *codec.EncodingWriter) error {
return tree.WriteRoots(w, b[:])
}
func (b HistoricalRootsProof) ByteLength() (out uint64) {
return historicalRootsProofLen * 32
}
func (b HistoricalRootsProof) FixedLength() uint64 {
return historicalRootsProofLen * 32
}
func (b *HistoricalRootsProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.ComplexVectorHTR(func(i uint64) tree.HTR {
if i < historicalRootsProofLen {
return &b[i]
}
return nil
}, historicalRootsProofLen)
}
type HistoricalRootsBlockProof struct {
BeaconBlockBodyProof BeaconBlockBodyProof `yaml:"beacon_block_body_proof" json:"beacon_block_body_proof"`
BeaconBlockBodyRoot common.Root `yaml:"beacon_block_body_root" json:"beacon_block_body_root"`
BeaconBlockHeaderProof BeaconBlockHeaderProof `yaml:"beacon_block_header_proof" json:"beacon_block_header_proof"`
BeaconBlockHeaderRoot common.Root `yaml:"beacon_block_header_root" json:"beacon_block_header_root"`
HistoricalRootsProof HistoricalRootsProof `yaml:"historical_roots_proof" json:"historical_roots_proof"`
Slot common.Slot `yaml:"slot" json:"slot"`
}
func (h *HistoricalRootsBlockProof) Deserialize(dr *codec.DecodingReader) error {
return dr.FixedLenContainer(
&h.BeaconBlockBodyProof,
&h.BeaconBlockBodyRoot,
&h.BeaconBlockHeaderProof,
&h.BeaconBlockHeaderProof,
&h.HistoricalRootsProof,
&h.Slot,
)
}
func (h *HistoricalRootsBlockProof) Serialize(w *codec.EncodingWriter) error {
return w.FixedLenContainer(
&h.BeaconBlockBodyProof,
&h.BeaconBlockBodyRoot,
&h.BeaconBlockHeaderProof,
&h.BeaconBlockHeaderProof,
&h.HistoricalRootsProof,
&h.Slot,
)
}
func (h *HistoricalRootsBlockProof) ByteLength(spec *common.Spec) uint64 {
return codec.ContainerLength(
&h.BeaconBlockBodyProof,
&h.BeaconBlockBodyRoot,
&h.BeaconBlockHeaderProof,
&h.BeaconBlockHeaderProof,
&h.HistoricalRootsProof,
&h.Slot,
)
}
func (h *HistoricalRootsBlockProof) FixedLength(spec *common.Spec) uint64 {
return codec.ContainerLength(
&h.BeaconBlockBodyProof,
&h.BeaconBlockBodyRoot,
&h.BeaconBlockHeaderProof,
&h.BeaconBlockHeaderProof,
&h.HistoricalRootsProof,
&h.Slot,
)
}
func (h *HistoricalRootsBlockProof) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&h.BeaconBlockBodyProof,
&h.BeaconBlockBodyRoot,
&h.BeaconBlockHeaderProof,
&h.BeaconBlockHeaderProof,
&h.HistoricalRootsProof,
&h.Slot,
)
}
type HistoricalRoots []common.Root
func (h *HistoricalRoots) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
return dr.List(func() codec.Deserializable {
i := len(*h)
*h = append(*h, common.Root{})
return &(*h)[i]
}, common.Root{}.ByteLength(), uint64(spec.HISTORICAL_ROOTS_LIMIT))
}
func (h HistoricalRoots) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
return w.List(func(i uint64) codec.Serializable {
return &h[i]
}, common.Root{}.ByteLength(), uint64(spec.HISTORICAL_ROOTS_LIMIT))
}
func (h HistoricalRoots) ByteLength(spec *common.Spec) uint64 {
return uint64(len(h)) * (common.Root{}.ByteLength())
}
func (h *HistoricalRoots) FixedLength(_ *common.Spec) uint64 {
return 0
}
func (h HistoricalRoots) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
length := uint64(len(h))
return hFn.ComplexListHTR(func(i uint64) tree.HTR {
if i < length {
return &h[i]
}
return nil
}, length, uint64(spec.HISTORICAL_ROOTS_LIMIT))
}
type BlockNumberKey view.Uint64View

View file

@ -1,542 +0,0 @@
package portalwire
import (
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/holiman/uint256"
)
// DiscV5API json-rpc spec
// https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/portal-network-specs/assembled-spec/jsonrpc/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false
type DiscV5API struct {
DiscV5 *discover.UDPv5
}
func NewDiscV5API(discV5 *discover.UDPv5) *DiscV5API {
return &DiscV5API{discV5}
}
type NodeInfo struct {
NodeId string `json:"nodeId"`
Enr string `json:"enr"`
Ip string `json:"ip"`
}
type RoutingTableInfo struct {
Buckets [][]string `json:"buckets"`
LocalNodeId string `json:"localNodeId"`
}
type DiscV5PongResp struct {
EnrSeq uint64 `json:"enrSeq"`
RecipientIP string `json:"recipientIP"`
RecipientPort uint16 `json:"recipientPort"`
}
type PortalPongResp struct {
EnrSeq uint32 `json:"enrSeq"`
DataRadius string `json:"dataRadius"`
}
type ContentInfo struct {
Content string `json:"content"`
UtpTransfer bool `json:"utpTransfer"`
}
type TraceContentResult struct {
Content string `json:"content"`
UtpTransfer bool `json:"utpTransfer"`
Trace Trace `json:"trace"`
}
type Trace struct {
Origin string `json:"origin"` // local node id
TargetId string `json:"targetId"` // target content id
ReceivedFrom string `json:"receivedFrom"` // the node id of which content from
Responses map[string]RespByNode `json:"responses"` // the node id and there response nodeIds
Metadata map[string]*NodeMetadata `json:"metadata"` // node id and there metadata object
StartedAtMs int `json:"startedAtMs"` // timestamp of the beginning of this request in milliseconds
Cancelled []string `json:"cancelled"` // the node ids which are send but cancelled
}
type NodeMetadata struct {
Enr string `json:"enr"`
Distance string `json:"distance"`
}
type RespByNode struct {
DurationMs int32 `json:"durationMs"`
RespondedWith []string `json:"respondedWith"`
}
type EnrsResp struct {
Enrs []string `json:"enrs"`
}
func (d *DiscV5API) NodeInfo() *NodeInfo {
n := d.DiscV5.LocalNode().Node()
return &NodeInfo{
NodeId: "0x" + n.ID().String(),
Enr: n.String(),
Ip: n.IP().String(),
}
}
func (d *DiscV5API) RoutingTableInfo() *RoutingTableInfo {
n := d.DiscV5.LocalNode().Node()
bucketNodes := d.DiscV5.RoutingTableInfo()
return &RoutingTableInfo{
Buckets: bucketNodes,
LocalNodeId: "0x" + n.ID().String(),
}
}
func (d *DiscV5API) AddEnr(enr string) (bool, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return false, err
}
// immediately add the node to the routing table
d.DiscV5.Table().AddInboundNode(n)
return true, nil
}
func (d *DiscV5API) GetEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := d.DiscV5.Table().GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
return true, nil
}
func (d *DiscV5API) DeleteEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := d.DiscV5.Table().GetNode(id)
if n == nil {
return false, errors.New("record not in local routing table")
}
d.DiscV5.Table().DeleteNode(n)
return true, nil
}
func (d *DiscV5API) LookupEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
enr := d.DiscV5.ResolveNodeId(id)
if enr == nil {
return "", errors.New("record not found in DHT lookup")
}
return enr.String(), nil
}
func (d *DiscV5API) Ping(enr string) (*DiscV5PongResp, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
pong, err := d.DiscV5.PingWithResp(n)
if err != nil {
return nil, err
}
return &DiscV5PongResp{
EnrSeq: pong.ENRSeq,
RecipientIP: pong.ToIP.String(),
RecipientPort: pong.ToPort,
}, nil
}
func (d *DiscV5API) FindNodes(enr string, distances []uint) ([]string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
findNodes, err := d.DiscV5.Findnode(n, distances)
if err != nil {
return nil, err
}
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (d *DiscV5API) TalkReq(enr string, protocol string, payload string) (string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return "", err
}
req, err := hexutil.Decode(payload)
if err != nil {
return "", err
}
talkResp, err := d.DiscV5.TalkRequest(n, protocol, req)
if err != nil {
return "", err
}
return hexutil.Encode(talkResp), nil
}
func (d *DiscV5API) RecursiveFindNodes(nodeId string) ([]string, error) {
findNodes := d.DiscV5.Lookup(enode.HexID(nodeId))
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
type PortalProtocolAPI struct {
portalProtocol *PortalProtocol
}
func NewPortalAPI(portalProtocol *PortalProtocol) *PortalProtocolAPI {
return &PortalProtocolAPI{
portalProtocol: portalProtocol,
}
}
func (p *PortalProtocolAPI) NodeInfo() *NodeInfo {
n := p.portalProtocol.localNode.Node()
return &NodeInfo{
NodeId: n.ID().String(),
Enr: n.String(),
Ip: n.IP().String(),
}
}
func (p *PortalProtocolAPI) RoutingTableInfo() *RoutingTableInfo {
n := p.portalProtocol.localNode.Node()
bucketNodes := p.portalProtocol.RoutingTableInfo()
return &RoutingTableInfo{
Buckets: bucketNodes,
LocalNodeId: "0x" + n.ID().String(),
}
}
func (p *PortalProtocolAPI) AddEnr(enr string) (bool, error) {
p.portalProtocol.Log.Debug("serving AddEnr", "enr", enr)
n, err := enode.ParseForAddEnr(enode.ValidSchemes, enr)
if err != nil {
return false, err
}
p.portalProtocol.AddEnr(n)
return true, nil
}
func (p *PortalProtocolAPI) AddEnrs(enrs []string) bool {
// Note: unspecified RPC, but useful for our local testnet test
for _, enr := range enrs {
n, err := enode.ParseForAddEnr(enode.ValidSchemes, enr)
if err != nil {
continue
}
p.portalProtocol.AddEnr(n)
}
return true
}
func (p *PortalProtocolAPI) GetEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
if id == p.portalProtocol.localNode.Node().ID() {
return p.portalProtocol.localNode.Node().String(), nil
}
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return "", errors.New("record not in local routing table")
}
return n.String(), nil
}
func (p *PortalProtocolAPI) DeleteEnr(nodeId string) (bool, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return false, err
}
n := p.portalProtocol.table.GetNode(id)
if n == nil {
return false, nil
}
p.portalProtocol.table.DeleteNode(n)
return true, nil
}
func (p *PortalProtocolAPI) LookupEnr(nodeId string) (string, error) {
id, err := enode.ParseID(nodeId)
if err != nil {
return "", err
}
enr := p.portalProtocol.ResolveNodeId(id)
if enr == nil {
return "", errors.New("record not found in DHT lookup")
}
return enr.String(), nil
}
func (p *PortalProtocolAPI) Ping(enr string) (*PortalPongResp, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
pong, err := p.portalProtocol.pingInner(n)
if err != nil {
return nil, err
}
customPayload := &PingPongCustomData{}
err = customPayload.UnmarshalSSZ(pong.CustomPayload)
if err != nil {
return nil, err
}
nodeRadius := new(uint256.Int)
err = nodeRadius.UnmarshalSSZ(customPayload.Radius)
if err != nil {
return nil, err
}
return &PortalPongResp{
EnrSeq: uint32(pong.EnrSeq),
DataRadius: nodeRadius.Hex(),
}, nil
}
func (p *PortalProtocolAPI) FindNodes(enr string, distances []uint) ([]string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
findNodes, err := p.portalProtocol.findNodes(n, distances)
if err != nil {
return nil, err
}
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (p *PortalProtocolAPI) FindContent(enr string, contentKey string) (interface{}, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return nil, err
}
contentKeyBytes, err := hexutil.Decode(contentKey)
if err != nil {
return nil, err
}
flag, findContent, err := p.portalProtocol.findContent(n, contentKeyBytes)
if err != nil {
return nil, err
}
switch flag {
case ContentRawSelector:
contentInfo := &ContentInfo{
Content: hexutil.Encode(findContent.([]byte)),
UtpTransfer: false,
}
p.portalProtocol.Log.Trace("FindContent", "contentInfo", contentInfo)
return contentInfo, nil
case ContentConnIdSelector:
contentInfo := &ContentInfo{
Content: hexutil.Encode(findContent.([]byte)),
UtpTransfer: true,
}
p.portalProtocol.Log.Trace("FindContent", "contentInfo", contentInfo)
return contentInfo, nil
default:
enrs := make([]string, 0)
for _, r := range findContent.([]*enode.Node) {
enrs = append(enrs, r.String())
}
p.portalProtocol.Log.Trace("FindContent", "enrs", enrs)
return &EnrsResp{
Enrs: enrs,
}, nil
}
}
func (p *PortalProtocolAPI) Offer(enr string, contentItems [][2]string) (string, error) {
n, err := enode.Parse(enode.ValidSchemes, enr)
if err != nil {
return "", err
}
entries := make([]*ContentEntry, 0, len(contentItems))
for _, contentItem := range contentItems {
contentKey, err := hexutil.Decode(contentItem[0])
if err != nil {
return "", err
}
contentValue, err := hexutil.Decode(contentItem[1])
if err != nil {
return "", err
}
contentEntry := &ContentEntry{
ContentKey: contentKey,
Content: contentValue,
}
entries = append(entries, contentEntry)
}
transientOfferRequest := &TransientOfferRequest{
Contents: entries,
}
offerReq := &OfferRequest{
Kind: TransientOfferRequestKind,
Request: transientOfferRequest,
}
accept, err := p.portalProtocol.offer(n, offerReq)
if err != nil {
return "", err
}
return hexutil.Encode(accept), nil
}
func (p *PortalProtocolAPI) RecursiveFindNodes(nodeId string) ([]string, error) {
findNodes := p.portalProtocol.Lookup(enode.HexID(nodeId))
enrs := make([]string, 0, len(findNodes))
for _, r := range findNodes {
enrs = append(enrs, r.String())
}
return enrs, nil
}
func (p *PortalProtocolAPI) RecursiveFindContent(contentKeyHex string) (*ContentInfo, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return nil, err
}
contentId := p.portalProtocol.toContentId(contentKey)
data, err := p.portalProtocol.Get(contentKey, contentId)
if err == nil {
return &ContentInfo{
Content: hexutil.Encode(data),
UtpTransfer: false,
}, err
}
p.portalProtocol.Log.Warn("find content err", "contextKey", hexutil.Encode(contentKey), "err", err)
content, utpTransfer, err := p.portalProtocol.ContentLookup(contentKey, contentId)
if err != nil {
return nil, err
}
return &ContentInfo{
Content: hexutil.Encode(content),
UtpTransfer: utpTransfer,
}, err
}
func (p *PortalProtocolAPI) LocalContent(contentKeyHex string) (string, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return "", err
}
contentId := p.portalProtocol.ToContentId(contentKey)
content, err := p.portalProtocol.Get(contentKey, contentId)
if err != nil {
return "", err
}
return hexutil.Encode(content), nil
}
func (p *PortalProtocolAPI) Store(contentKeyHex string, contextHex string) (bool, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return false, err
}
contentId := p.portalProtocol.ToContentId(contentKey)
if !p.portalProtocol.InRange(contentId) {
return false, nil
}
content, err := hexutil.Decode(contextHex)
if err != nil {
return false, err
}
err = p.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
return false, err
}
return true, nil
}
func (p *PortalProtocolAPI) Gossip(contentKeyHex, contentHex string) (int, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return 0, err
}
content, err := hexutil.Decode(contentHex)
if err != nil {
return 0, err
}
id := p.portalProtocol.Self().ID()
return p.portalProtocol.Gossip(&id, [][]byte{contentKey}, [][]byte{content})
}
func (p *PortalProtocolAPI) TraceRecursiveFindContent(contentKeyHex string) (*TraceContentResult, error) {
contentKey, err := hexutil.Decode(contentKeyHex)
if err != nil {
return nil, err
}
contentId := p.portalProtocol.toContentId(contentKey)
return p.portalProtocol.TraceContentLookup(contentKey, contentId)
}

View file

@ -1,172 +0,0 @@
package portalwire
import (
"net"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/nat"
)
const (
portMapDuration = 10 * time.Minute
portMapRefreshInterval = 8 * time.Minute
portMapRetryInterval = 5 * time.Minute
extipRetryInterval = 2 * time.Minute
)
type portMapping struct {
protocol string
name string
port int
// for use by the portMappingLoop goroutine:
extPort int // the mapped port returned by the NAT interface
nextTime mclock.AbsTime
}
// setupPortMapping starts the port mapping loop if necessary.
// Note: this needs to be called after the LocalNode instance has been set on the server.
func (p *PortalProtocol) setupPortMapping() {
// portMappingRegister will receive up to two values: one for the TCP port if
// listening is enabled, and one more for enabling UDP port mapping if discovery is
// enabled. We make it buffered to avoid blocking setup while a mapping request is in
// progress.
p.portMappingRegister = make(chan *portMapping, 2)
switch p.NAT.(type) {
case nil:
// No NAT interface configured.
go p.consumePortMappingRequests()
case nat.ExtIP:
// ExtIP doesn't block, set the IP right away.
ip, _ := p.NAT.ExternalIP()
p.localNode.SetStaticIP(ip)
go p.consumePortMappingRequests()
case nat.STUN:
// STUN doesn't block, set the IP right away.
ip, _ := p.NAT.ExternalIP()
p.localNode.SetStaticIP(ip)
go p.consumePortMappingRequests()
default:
go p.portMappingLoop()
}
}
func (p *PortalProtocol) consumePortMappingRequests() {
for {
select {
case <-p.closeCtx.Done():
return
case <-p.portMappingRegister:
}
}
}
// portMappingLoop manages port mappings for UDP and TCP.
func (p *PortalProtocol) portMappingLoop() {
newLogger := func(proto string, e int, i int) log.Logger {
return log.New("proto", proto, "extport", e, "intport", i, "interface", p.NAT)
}
var (
mappings = make(map[string]*portMapping, 2)
refresh = mclock.NewAlarm(p.clock)
extip = mclock.NewAlarm(p.clock)
lastExtIP net.IP
)
extip.Schedule(p.clock.Now())
defer func() {
refresh.Stop()
extip.Stop()
for _, m := range mappings {
if m.extPort != 0 {
log := newLogger(m.protocol, m.extPort, m.port)
log.Debug("Deleting port mapping")
p.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
}
}
}()
for {
// Schedule refresh of existing mappings.
for _, m := range mappings {
refresh.Schedule(m.nextTime)
}
select {
case <-p.closeCtx.Done():
return
case <-extip.C():
extip.Schedule(p.clock.Now().Add(extipRetryInterval))
ip, err := p.NAT.ExternalIP()
if err != nil {
log.Debug("Couldn't get external IP", "err", err, "interface", p.NAT)
} else if !ip.Equal(lastExtIP) {
log.Debug("External IP changed", "ip", extip, "interface", p.NAT)
} else {
continue
}
// Here, we either failed to get the external IP, or it has changed.
lastExtIP = ip
p.localNode.SetStaticIP(ip)
p.Log.Debug("set static ip in nat", "ip", p.localNode.Node().IP().String())
// Ensure port mappings are refreshed in case we have moved to a new network.
for _, m := range mappings {
m.nextTime = p.clock.Now()
}
case m := <-p.portMappingRegister:
if m.protocol != "TCP" && m.protocol != "UDP" {
panic("unknown NAT protocol name: " + m.protocol)
}
mappings[m.protocol] = m
m.nextTime = p.clock.Now()
case <-refresh.C():
for _, m := range mappings {
if p.clock.Now() < m.nextTime {
continue
}
external := m.port
if m.extPort != 0 {
external = m.extPort
}
log := newLogger(m.protocol, external, m.port)
log.Trace("Attempting port mapping")
port, err := p.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration)
if err != nil {
log.Debug("Couldn't add port mapping", "err", err)
m.extPort = 0
m.nextTime = p.clock.Now().Add(portMapRetryInterval)
continue
}
// It was mapped!
m.extPort = int(port)
m.nextTime = p.clock.Now().Add(portMapRefreshInterval)
if external != m.extPort {
log = newLogger(m.protocol, m.extPort, m.port)
log.Info("NAT mapped alternative port")
} else {
log.Info("NAT mapped port")
}
// Update port in local ENR.
switch m.protocol {
case "TCP":
p.localNode.Set(enr.TCP(m.extPort))
case "UDP":
p.localNode.SetFallbackUDP(m.extPort)
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,217 +0,0 @@
package portalwire
import (
"database/sql"
"errors"
"os"
"path"
"slices"
"strings"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)
type portalMetrics struct {
messagesReceivedAccept metrics.Meter
messagesReceivedNodes metrics.Meter
messagesReceivedFindNodes metrics.Meter
messagesReceivedFindContent metrics.Meter
messagesReceivedContent metrics.Meter
messagesReceivedOffer metrics.Meter
messagesReceivedPing metrics.Meter
messagesReceivedPong metrics.Meter
messagesSentAccept metrics.Meter
messagesSentNodes metrics.Meter
messagesSentFindNodes metrics.Meter
messagesSentFindContent metrics.Meter
messagesSentContent metrics.Meter
messagesSentOffer metrics.Meter
messagesSentPing metrics.Meter
messagesSentPong metrics.Meter
utpInFailConn metrics.Counter
utpInFailRead metrics.Counter
utpInFailDeadline metrics.Counter
utpInSuccess metrics.Counter
utpOutFailConn metrics.Counter
utpOutFailWrite metrics.Counter
utpOutFailDeadline metrics.Counter
utpOutSuccess metrics.Counter
contentDecodedTrue metrics.Counter
contentDecodedFalse metrics.Counter
}
func newPortalMetrics(protocolName string) *portalMetrics {
return &portalMetrics{
messagesReceivedAccept: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/accept", nil),
messagesReceivedNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/nodes", nil),
messagesReceivedFindNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/find_nodes", nil),
messagesReceivedFindContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/find_content", nil),
messagesReceivedContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/content", nil),
messagesReceivedOffer: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/offer", nil),
messagesReceivedPing: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/ping", nil),
messagesReceivedPong: metrics.NewRegisteredMeter("portal/"+protocolName+"/received/pong", nil),
messagesSentAccept: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/accept", nil),
messagesSentNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/nodes", nil),
messagesSentFindNodes: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/find_nodes", nil),
messagesSentFindContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/find_content", nil),
messagesSentContent: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/content", nil),
messagesSentOffer: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/offer", nil),
messagesSentPing: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/ping", nil),
messagesSentPong: metrics.NewRegisteredMeter("portal/"+protocolName+"/sent/pong", nil),
utpInFailConn: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_conn", nil),
utpInFailRead: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_read", nil),
utpInFailDeadline: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/fail_deadline", nil),
utpInSuccess: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/inbound/success", nil),
utpOutFailConn: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_conn", nil),
utpOutFailWrite: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_write", nil),
utpOutFailDeadline: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/fail_deadline", nil),
utpOutSuccess: metrics.NewRegisteredCounter("portal/"+protocolName+"/utp/outbound/success", nil),
contentDecodedTrue: metrics.NewRegisteredCounter("portal/"+protocolName+"/content/decoded/true", nil),
contentDecodedFalse: metrics.NewRegisteredCounter("portal/"+protocolName+"/content/decoded/false", nil),
}
}
type networkFileMetric struct {
filename string
metric metrics.Gauge
file *os.File
network string
}
type PortalStorageMetrics struct {
RadiusRatio metrics.GaugeFloat64
EntriesCount metrics.Gauge
ContentStorageUsage metrics.Gauge
}
const (
countEntrySql = "SELECT COUNT(1) FROM kvstore;"
contentStorageUsageSql = "SELECT SUM( length(value) ) FROM kvstore;"
)
// CollectPortalMetrics periodically collects various metrics about system entities.
func CollectPortalMetrics(refresh time.Duration, networks []string, dataDir string) {
// Short circuit if the metrics system is disabled
if !metrics.Enabled {
return
}
// Define the various metrics to collect
var (
historyTotalStorage = metrics.GetOrRegisterGauge("portal/history/total_storage", nil)
beaconTotalStorage = metrics.GetOrRegisterGauge("portal/beacon/total_storage", nil)
stateTotalStorage = metrics.GetOrRegisterGauge("portal/state/total_storage", nil)
)
var metricsArr []*networkFileMetric
if slices.Contains(networks, History.Name()) {
dbPath := path.Join(dataDir, History.Name())
metricsArr = append(metricsArr, &networkFileMetric{
filename: path.Join(dbPath, History.Name()+".sqlite"),
metric: historyTotalStorage,
network: History.Name(),
})
}
if slices.Contains(networks, Beacon.Name()) {
dbPath := path.Join(dataDir, Beacon.Name())
metricsArr = append(metricsArr, &networkFileMetric{
filename: path.Join(dbPath, Beacon.Name()+".sqlite"),
metric: beaconTotalStorage,
network: Beacon.Name(),
})
}
if slices.Contains(networks, State.Name()) {
dbPath := path.Join(dataDir, State.Name())
metricsArr = append(metricsArr, &networkFileMetric{
filename: path.Join(dbPath, State.Name()+".sqlite"),
metric: stateTotalStorage,
network: State.Name(),
})
}
for {
for _, m := range metricsArr {
var err error = nil
if m.file == nil {
m.file, err = os.OpenFile(m.filename, os.O_RDONLY, 0600)
if err != nil {
log.Debug("Could not open file", "network", m.network, "file", m.filename, "metric", "total_storage", "err", err)
}
}
if m.file != nil && err == nil {
stat, err := m.file.Stat()
if err != nil {
log.Warn("Could not get file stat", "network", m.network, "file", m.filename, "metric", "total_storage", "err", err)
}
if err == nil {
m.metric.Update(stat.Size())
}
}
}
time.Sleep(refresh)
}
}
func NewPortalStorageMetrics(network string, db *sql.DB) (*PortalStorageMetrics, error) {
if !metrics.Enabled {
return nil, nil
}
if network != History.Name() && network != Beacon.Name() && network != State.Name() {
log.Debug("Unknow network for metrics", "network", network)
return nil, errors.New("unknow network for metrics")
}
var countSql string
var contentSql string
if network == Beacon.Name() {
countSql = strings.Replace(countEntrySql, "kvstore", "beacon", 1)
contentSql = strings.Replace(contentStorageUsageSql, "kvstore", "beacon", 1)
contentSql = strings.Replace(contentSql, "value", "content_value", 1)
} else {
countSql = countEntrySql
contentSql = contentStorageUsageSql
}
storageMetrics := &PortalStorageMetrics{}
storageMetrics.RadiusRatio = metrics.NewRegisteredGaugeFloat64("portal/"+network+"/radius_ratio", nil)
storageMetrics.RadiusRatio.Update(1)
storageMetrics.EntriesCount = metrics.NewRegisteredGauge("portal/"+network+"/entry_count", nil)
log.Debug("Counting entities in " + network + " storage for metrics")
var res = new(int64)
q := db.QueryRow(countSql)
if errors.Is(q.Err(), sql.ErrNoRows) {
storageMetrics.EntriesCount.Update(0)
} else if q.Err() != nil {
log.Error("Querry execution error", "network", network, "metric", "entry_count", "err", q.Err())
return nil, q.Err()
} else {
q.Scan(res)
storageMetrics.EntriesCount.Update(*res)
}
storageMetrics.ContentStorageUsage = metrics.NewRegisteredGauge("portal/"+network+"/content_storage", nil)
log.Debug("Counting storage usage (bytes) in " + network + " for metrics")
var res2 = new(int64)
q2 := db.QueryRow(contentSql)
if errors.Is(q2.Err(), sql.ErrNoRows) {
storageMetrics.ContentStorageUsage.Update(0)
} else if q2.Err() != nil {
log.Error("Querry execution error", "network", network, "metric", "entry_count", "err", q2.Err())
return nil, q2.Err()
} else {
q2.Scan(res2)
storageMetrics.ContentStorageUsage.Update(*res2)
}
return storageMetrics, nil
}

View file

@ -1,513 +0,0 @@
package portalwire
import (
"context"
"crypto/ecdsa"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/optimism-java/utp-go"
"github.com/optimism-java/utp-go/libutp"
"github.com/prysmaticlabs/go-bitfield"
"golang.org/x/exp/slices"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
assert "github.com/stretchr/testify/require"
)
func setupLocalPortalNode(addr string, bootNodes []*enode.Node) (*PortalProtocol, error) {
conf := DefaultPortalProtocolConfig()
conf.NAT = nil
if addr != "" {
conf.ListenAddr = addr
}
if bootNodes != nil {
conf.BootstrapNodes = bootNodes
}
addr1, err := net.ResolveUDPAddr("udp", conf.ListenAddr)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr1)
if err != nil {
return nil, err
}
privKey := newkey()
discCfg := discover.Config{
PrivateKey: privKey,
NetRestrict: conf.NetRestrict,
Bootnodes: conf.BootstrapNodes,
}
nodeDB, err := enode.OpenDB(conf.NodeDBPath)
if err != nil {
return nil, err
}
localNode := enode.NewLocalNode(nodeDB, privKey)
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(Tag)
if conf.NAT == nil {
var addrs []net.Addr
addrs, err = net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
}
}
}
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err
}
utpSocket := NewPortalUtp(context.Background(), conf, discV5, conn)
contentQueue := make(chan *ContentElement, 50)
portalProtocol, err := NewPortalProtocol(
conf,
History,
privKey,
conn,
localNode,
discV5,
utpSocket,
&storage.MockStorage{Db: make(map[string][]byte)},
contentQueue)
if err != nil {
return nil, err
}
return portalProtocol, nil
}
func TestPortalWireProtocolUdp(t *testing.T) {
node1, err := setupLocalPortalNode(":8777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":8778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
node3, err := setupLocalPortalNode(":8779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
cid1 := libutp.ReceConnId(12)
cid2 := libutp.ReceConnId(116)
cliSendMsgWithCid1 := "there are connection id : 12!"
cliSendMsgWithCid2 := "there are connection id: 116!"
serverEchoWithCid := "accept connection sends back msg: echo"
largeTestContent := make([]byte, 1199)
_, err = rand.Read(largeTestContent)
assert.NoError(t, err)
var workGroup sync.WaitGroup
var acceptGroup sync.WaitGroup
workGroup.Add(4)
acceptGroup.Add(1)
go func() {
var acceptConn *utp.Conn
defer func() {
workGroup.Done()
_ = acceptConn.Close()
}()
acceptConn, err := node1.Utp.AcceptWithCid(context.Background(), node2.localNode.ID(), cid1)
if err != nil {
panic(err)
}
acceptGroup.Done()
buf := make([]byte, 100)
n, err := acceptConn.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, cliSendMsgWithCid1, string(buf[:n]))
_, err = acceptConn.Write([]byte(serverEchoWithCid))
if err != nil {
panic(err)
}
}()
go func() {
var connId2Conn net.Conn
defer func() {
workGroup.Done()
_ = connId2Conn.Close()
}()
connId2Conn, err := node1.Utp.AcceptWithCid(context.Background(), node2.localNode.ID(), cid2)
if err != nil {
panic(err)
}
buf := make([]byte, 100)
n, err := connId2Conn.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, cliSendMsgWithCid2, string(buf[:n]))
_, err = connId2Conn.Write(largeTestContent)
if err != nil {
panic(err)
}
}()
go func() {
var connWithConnId net.Conn
defer func() {
workGroup.Done()
if connWithConnId != nil {
_ = connWithConnId.Close()
}
}()
connWithConnId, err = node2.Utp.DialWithCid(context.Background(), node1.localNode.Node(), cid1.SendId())
if err != nil {
panic(err)
}
_, err = connWithConnId.Write([]byte(cliSendMsgWithCid1))
if err != nil && err != io.EOF {
panic(err)
}
buf := make([]byte, 100)
n, err := connWithConnId.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
assert.Equal(t, serverEchoWithCid, string(buf[:n]))
}()
go func() {
var ConnId2Conn net.Conn
defer func() {
workGroup.Done()
if ConnId2Conn != nil {
_ = ConnId2Conn.Close()
}
}()
ConnId2Conn, err = node2.Utp.DialWithCid(context.Background(), node1.localNode.Node(), cid2.SendId())
if err != nil && err != io.EOF {
panic(err)
}
_, err = ConnId2Conn.Write([]byte(cliSendMsgWithCid2))
if err != nil {
panic(err)
}
data := make([]byte, 0)
buf := make([]byte, 1024)
for {
var n int
n, err = ConnId2Conn.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
}
data = append(data, buf[:n]...)
}
assert.Equal(t, largeTestContent, data)
}()
workGroup.Wait()
node1.Stop()
node2.Stop()
node3.Stop()
}
func TestPortalWireProtocol(t *testing.T) {
node1, err := setupLocalPortalNode(":7777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LevelDebug)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":7778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LevelDebug)
err = node2.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
node3, err := setupLocalPortalNode(":7779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LevelDebug)
err = node3.Start()
assert.NoError(t, err)
time.Sleep(12 * time.Second)
slices.ContainsFunc(node1.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
slices.ContainsFunc(node1.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node2.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node3.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node1.localNode.Node().ID()
})
slices.ContainsFunc(node3.table.NodeList(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
})
err = node1.storage.Put(nil, node1.toContentId([]byte("test_key")), []byte("test_value"))
assert.NoError(t, err)
flag, content, err := node2.findContent(node1.localNode.Node(), []byte("test_key"))
assert.NoError(t, err)
assert.Equal(t, ContentRawSelector, flag)
assert.Equal(t, []byte("test_value"), content)
flag, content, err = node2.findContent(node3.localNode.Node(), []byte("test_key"))
assert.NoError(t, err)
assert.Equal(t, ContentEnrsSelector, flag)
assert.Equal(t, 1, len(content.([]*enode.Node)))
assert.Equal(t, node1.localNode.Node().ID(), content.([]*enode.Node)[0].ID())
// create a byte slice of length 1199 and fill it with random data
// this will be used as a test content
largeTestContent := make([]byte, 2000)
_, err = rand.Read(largeTestContent)
assert.NoError(t, err)
err = node1.storage.Put(nil, node1.toContentId([]byte("large_test_key")), largeTestContent)
assert.NoError(t, err)
flag, content, err = node2.findContent(node1.localNode.Node(), []byte("large_test_key"))
assert.NoError(t, err)
assert.Equal(t, largeTestContent, content)
assert.Equal(t, ContentConnIdSelector, flag)
testEntry1 := &ContentEntry{
ContentKey: []byte("test_entry1"),
Content: []byte("test_entry1_content"),
}
testEntry2 := &ContentEntry{
ContentKey: []byte("test_entry2"),
Content: []byte("test_entry2_content"),
}
testTransientOfferRequest := &TransientOfferRequest{
Contents: []*ContentEntry{testEntry1, testEntry2},
}
offerRequest := &OfferRequest{
Kind: TransientOfferRequestKind,
Request: testTransientOfferRequest,
}
contentKeys, err := node1.offer(node3.localNode.Node(), offerRequest)
assert.Equal(t, uint64(2), bitfield.Bitlist(contentKeys).Count())
assert.NoError(t, err)
contentElement := <-node3.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testEntry1.ContentKey, contentElement.ContentKeys[0])
assert.Equal(t, testEntry1.Content, contentElement.Contents[0])
assert.Equal(t, testEntry2.ContentKey, contentElement.ContentKeys[1])
assert.Equal(t, testEntry2.Content, contentElement.Contents[1])
testGossipContentKeys := [][]byte{[]byte("test_gossip_content_keys"), []byte("test_gossip_content_keys2")}
testGossipContent := [][]byte{[]byte("test_gossip_content"), []byte("test_gossip_content2")}
id := node1.Self().ID()
gossip, err := node1.Gossip(&id, testGossipContentKeys, testGossipContent)
assert.NoError(t, err)
assert.Equal(t, 2, gossip)
contentElement = <-node2.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testGossipContentKeys[0], contentElement.ContentKeys[0])
assert.Equal(t, testGossipContent[0], contentElement.Contents[0])
assert.Equal(t, testGossipContentKeys[1], contentElement.ContentKeys[1])
assert.Equal(t, testGossipContent[1], contentElement.Contents[1])
contentElement = <-node3.contentQueue
assert.Equal(t, node1.localNode.Node().ID(), contentElement.Node)
assert.Equal(t, testGossipContentKeys[0], contentElement.ContentKeys[0])
assert.Equal(t, testGossipContent[0], contentElement.Contents[0])
assert.Equal(t, testGossipContentKeys[1], contentElement.ContentKeys[1])
assert.Equal(t, testGossipContent[1], contentElement.Contents[1])
node1.Stop()
node2.Stop()
node3.Stop()
}
func TestCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
defer func() {
t.Log("goroutine cancel")
}()
time.Sleep(time.Second * 5)
}(ctx)
cancel()
t.Log("after main cancel")
time.Sleep(time.Second * 3)
}
func TestContentLookup(t *testing.T) {
node1, err := setupLocalPortalNode(":17777", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":17778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
fmt.Println(node2.localNode.Node().String())
node3, err := setupLocalPortalNode(":17779", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
defer func() {
node1.Stop()
node2.Stop()
node3.Stop()
}()
contentKey := []byte{0x3, 0x4}
content := []byte{0x1, 0x2}
contentId := node1.toContentId(contentKey)
err = node3.storage.Put(nil, contentId, content)
assert.NoError(t, err)
res, _, err := node1.ContentLookup(contentKey, contentId)
assert.NoError(t, err)
assert.Equal(t, res, content)
nonExist := []byte{0x2, 0x4}
res, _, err = node1.ContentLookup(nonExist, node1.toContentId(nonExist))
assert.Equal(t, ContentNotFound, err)
assert.Nil(t, res)
}
func TestTraceContentLookup(t *testing.T) {
node1, err := setupLocalPortalNode(":17787", nil)
assert.NoError(t, err)
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
node2, err := setupLocalPortalNode(":17788", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LvlTrace)
err = node2.Start()
assert.NoError(t, err)
node3, err := setupLocalPortalNode(":17789", []*enode.Node{node2.localNode.Node()})
assert.NoError(t, err)
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
defer node1.Stop()
defer node2.Stop()
defer node3.Stop()
contentKey := []byte{0x3, 0x4}
content := []byte{0x1, 0x2}
contentId := node1.toContentId(contentKey)
err = node1.storage.Put(nil, contentId, content)
assert.NoError(t, err)
node1Id := hexutil.Encode(node1.Self().ID().Bytes())
node2Id := hexutil.Encode(node2.Self().ID().Bytes())
node3Id := hexutil.Encode(node3.Self().ID().Bytes())
res, err := node3.TraceContentLookup(contentKey, contentId)
assert.NoError(t, err)
assert.Equal(t, res.Content, hexutil.Encode(content))
assert.Equal(t, res.UtpTransfer, false)
assert.Equal(t, res.Trace.Origin, node3Id)
assert.Equal(t, res.Trace.TargetId, hexutil.Encode(contentId))
assert.Equal(t, res.Trace.ReceivedFrom, node1Id)
// check nodeMeta
node1Meta := res.Trace.Metadata[node1Id]
assert.Equal(t, node1Meta.Enr, node1.Self().String())
dis := node1.Distance(node1.Self().ID(), enode.ID(contentId))
assert.Equal(t, node1Meta.Distance, hexutil.Encode(dis[:]))
node2Meta := res.Trace.Metadata[node2Id]
assert.Equal(t, node2Meta.Enr, node2.Self().String())
dis = node2.Distance(node2.Self().ID(), enode.ID(contentId))
assert.Equal(t, node2Meta.Distance, hexutil.Encode(dis[:]))
node3Meta := res.Trace.Metadata[node3Id]
assert.Equal(t, node3Meta.Enr, node3.Self().String())
dis = node3.Distance(node3.Self().ID(), enode.ID(contentId))
assert.Equal(t, node3Meta.Distance, hexutil.Encode(dis[:]))
// check response
node3Response := res.Trace.Responses[node3Id]
assert.Equal(t, node3Response.RespondedWith, []string{node2Id})
node2Response := res.Trace.Responses[node2Id]
assert.Equal(t, node2Response.RespondedWith, []string{node1Id})
node1Response := res.Trace.Responses[node1Id]
assert.Equal(t, node1Response.RespondedWith, ([]string)(nil))
}
func newkey() *ecdsa.PrivateKey {
key, err := crypto.GenerateKey()
if err != nil {
panic("couldn't generate key: " + err.Error())
}
return key
}

View file

@ -1,138 +0,0 @@
package portalwire
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/optimism-java/utp-go"
"github.com/optimism-java/utp-go/libutp"
"go.uber.org/zap"
)
type PortalUtp struct {
ctx context.Context
log log.Logger
discV5 *discover.UDPv5
conn discover.UDPConn
ListenAddr string
listener *utp.Listener
utpSm *utp.SocketManager
packetRouter *utp.PacketRouter
lAddr *utp.Addr
startOnce sync.Once
}
func NewPortalUtp(ctx context.Context, config *PortalProtocolConfig, discV5 *discover.UDPv5, conn discover.UDPConn) *PortalUtp {
return &PortalUtp{
ctx: ctx,
log: log.New("protocol", "utp", "local", conn.LocalAddr().String()),
discV5: discV5,
conn: conn,
ListenAddr: config.ListenAddr,
}
}
func (p *PortalUtp) Start() error {
var err error
go p.startOnce.Do(func() {
var logger *zap.Logger
if p.log.Enabled(p.ctx, log.LevelDebug) || p.log.Enabled(p.ctx, log.LevelTrace) {
logger, err = zap.NewDevelopmentConfig().Build()
} else {
logger, err = zap.NewProductionConfig().Build()
}
if err != nil {
return
}
laddr := p.getLocalAddr()
p.packetRouter = utp.NewPacketRouter(p.packetRouterFunc)
p.utpSm, err = utp.NewSocketManagerWithOptions(
"utp",
laddr,
utp.WithContext(p.ctx),
utp.WithLogger(logger.Named(p.ListenAddr)),
utp.WithPacketRouter(p.packetRouter),
utp.WithMaxPacketSize(1145))
if err != nil {
return
}
p.listener, err = utp.ListenUTPOptions("utp", (*utp.Addr)(laddr), utp.WithSocketManager(p.utpSm))
if err != nil {
return
}
p.lAddr = p.listener.Addr().(*utp.Addr)
// register discv5 listener
p.discV5.RegisterTalkHandler(string(Utp), p.handleUtpTalkRequest)
})
return err
}
func (p *PortalUtp) Stop() {
err := p.listener.Close()
if err != nil {
p.log.Error("close utp listener has error", "error", err)
}
p.discV5.Close()
}
func (p *PortalUtp) DialWithCid(ctx context.Context, dest *enode.Node, connId uint16) (net.Conn, error) {
raddr := &utp.Addr{IP: dest.IP(), Port: dest.UDP()}
p.log.Debug("will connect to: ", "nodeId", dest.ID().String(), "connId", connId)
conn, err := utp.DialUTPOptions("utp", p.lAddr, raddr, utp.WithContext(ctx), utp.WithSocketManager(p.utpSm), utp.WithConnId(connId))
return conn, err
}
func (p *PortalUtp) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
raddr := &utp.Addr{IP: dest.IP(), Port: dest.UDP()}
p.log.Info("will connect to: ", "addr", raddr.String())
conn, err := utp.DialUTPOptions("utp", p.lAddr, raddr, utp.WithContext(ctx), utp.WithSocketManager(p.utpSm))
return conn, err
}
func (p *PortalUtp) AcceptWithCid(ctx context.Context, nodeId enode.ID, cid *libutp.ConnId) (*utp.Conn, error) {
p.log.Debug("will accept from: ", "nodeId", nodeId.String(), "sendId", cid.SendId(), "recvId", cid.RecvId())
return p.listener.AcceptUTPContext(ctx, nodeId, cid)
}
func (p *PortalUtp) Accept(ctx context.Context) (*utp.Conn, error) {
return p.listener.AcceptUTPContext(ctx, enode.ID{}, nil)
}
func (p *PortalUtp) getLocalAddr() *net.UDPAddr {
laddr := p.conn.LocalAddr().(*net.UDPAddr)
p.log.Debug("UDP listener up", "addr", laddr)
return laddr
}
func (p *PortalUtp) packetRouterFunc(buf []byte, id enode.ID, addr *net.UDPAddr) (int, error) {
p.log.Info("will send to target data", "nodeId", id.String(), "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
if n, ok := p.discV5.GetCachedNode(addr.String()); ok {
//_, err := p.DiscV5.TalkRequestToID(id, addr, string(portalwire.UTPNetwork), buf)
req := &v5wire.TalkRequest{Protocol: string(Utp), Message: buf}
p.discV5.SendFromAnotherThreadWithNode(n, netip.AddrPortFrom(netutil.IPToAddr(addr.IP), uint16(addr.Port)), req)
return len(buf), nil
} else {
p.log.Warn("not found target node info", "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
return 0, fmt.Errorf("not found target node id")
}
}
func (p *PortalUtp) handleUtpTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
p.log.Trace("receive utp data", "nodeId", id.String(), "addr", addr, "msg-length", len(msg))
p.packetRouter.ReceiveMessage(msg, &utp.NodeInfo{Id: id, Addr: addr})
return []byte("")
}

View file

@ -1,312 +0,0 @@
package portalwire
import (
ssz "github.com/ferranbt/fastssz"
)
// note: We changed the generated file since fastssz issues which can't be passed by the CI, so we commented the go:generate line
///go:generate sszgen --path types.go --exclude-objs Content,Enrs,ContentKV
// Message codes for the portal protocol.
const (
PING byte = 0x00
PONG byte = 0x01
FINDNODES byte = 0x02
NODES byte = 0x03
FINDCONTENT byte = 0x04
CONTENT byte = 0x05
OFFER byte = 0x06
ACCEPT byte = 0x07
)
// Content selectors for the portal protocol.
const (
ContentConnIdSelector byte = 0x00
ContentRawSelector byte = 0x01
ContentEnrsSelector byte = 0x02
)
const (
ContentKeysLimit = 64
// OfferMessageOverhead overhead of content message is a result of 1byte for kind enum, and
// 4 bytes for offset in ssz serialization
OfferMessageOverhead = 5
// PerContentKeyOverhead each key in ContentKeysList has uint32 offset which results in 4 bytes per
// key overhead when serialized
PerContentKeyOverhead = 4
)
type ProtocolId []byte
var (
State ProtocolId = []byte{0x50, 0x0A}
History ProtocolId = []byte{0x50, 0x0B}
Beacon ProtocolId = []byte{0x50, 0x0C}
CanonicalIndices ProtocolId = []byte{0x50, 0x0D}
VerkleState ProtocolId = []byte{0x50, 0x0E}
TransactionGossip ProtocolId = []byte{0x50, 0x0F}
Utp ProtocolId = []byte{0x75, 0x74, 0x70}
)
var protocolName = map[string]string{
string(State): "state",
string(History): "history",
string(Beacon): "beacon",
string(CanonicalIndices): "canonical indices",
string(VerkleState): "verkle state",
string(TransactionGossip): "transaction gossip",
}
func (p ProtocolId) Name() string {
return protocolName[string(p)]
}
type ContentKV struct {
ContentKey []byte
Content []byte
}
// Request messages for the portal protocol.
type (
PingPongCustomData struct {
Radius []byte `ssz-size:"32"`
}
Ping struct {
EnrSeq uint64
CustomPayload []byte `ssz-max:"2048"`
}
FindNodes struct {
Distances [][2]byte `ssz-max:"256,2" ssz-size:"?,2"`
}
FindContent struct {
ContentKey []byte `ssz-max:"2048"`
}
Offer struct {
ContentKeys [][]byte `ssz-max:"64,2048"`
}
)
// Response messages for the portal protocol.
type (
Pong struct {
EnrSeq uint64
CustomPayload []byte `ssz-max:"2048"`
}
Nodes struct {
Total uint8
Enrs [][]byte `ssz-max:"32,2048"`
}
ConnectionId struct {
Id []byte `ssz-size:"2"`
}
Content struct {
Content []byte `ssz-max:"2048"`
}
Enrs struct {
Enrs [][]byte `ssz-max:"32,2048"`
}
Accept struct {
ConnectionId []byte `ssz-size:"2"`
ContentKeys []byte `ssz:"bitlist" ssz-max:"64"`
}
)
// MarshalSSZ ssz marshals the Content object
func (c *Content) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(c)
}
// MarshalSSZTo ssz marshals the Content object to a target array
func (c *Content) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'Content'
if size := len(c.Content); size > 2048 {
err = ssz.ErrBytesLengthFn("Content.Content", size, 2048)
return
}
dst = append(dst, c.Content...)
return
}
// UnmarshalSSZ ssz unmarshals the Content object
func (c *Content) UnmarshalSSZ(buf []byte) error {
var err error
tail := buf
// Field (0) 'Content'
{
buf = tail[:]
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(c.Content) == 0 {
c.Content = make([]byte, 0, len(buf))
}
c.Content = append(c.Content, buf...)
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Content object
func (c *Content) SizeSSZ() (size int) {
// Field (0) 'Content'
return len(c.Content)
}
// HashTreeRoot ssz hashes the Content object
func (c *Content) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(c)
}
// HashTreeRootWith ssz hashes the Content object with a hasher
func (c *Content) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Content'
{
elemIndx := hh.Index()
byteLen := uint64(len(c.Content))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.Append(c.Content)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Content object
func (c *Content) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(c)
}
// MarshalSSZ ssz marshals the Enrs object
func (e *Enrs) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(e)
}
// MarshalSSZTo ssz marshals the Enrs object to a target array
func (e *Enrs) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(0)
// Field (0) 'Enrs'
if size := len(e.Enrs); size > 32 {
err = ssz.ErrListTooBigFn("Enrs.Enrs", size, 32)
return
}
{
offset = 4 * len(e.Enrs)
for ii := 0; ii < len(e.Enrs); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(e.Enrs[ii])
}
}
for ii := 0; ii < len(e.Enrs); ii++ {
if size := len(e.Enrs[ii]); size > 2048 {
err = ssz.ErrBytesLengthFn("Enrs.Enrs[ii]", size, 2048)
return
}
dst = append(dst, e.Enrs[ii]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the Enrs object
func (e *Enrs) UnmarshalSSZ(buf []byte) error {
var err error
tail := buf
// Field (0) 'Enrs'
{
buf = tail[:]
num, err := ssz.DecodeDynamicLength(buf, 32)
if err != nil {
return err
}
e.Enrs = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(e.Enrs[indx]) == 0 {
e.Enrs[indx] = make([]byte, 0, len(buf))
}
e.Enrs[indx] = append(e.Enrs[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Enrs object
func (e *Enrs) SizeSSZ() (size int) {
size = 0
// Field (0) 'Enrs'
for ii := 0; ii < len(e.Enrs); ii++ {
size += 4
size += len(e.Enrs[ii])
}
return
}
// HashTreeRoot ssz hashes the Enrs object
func (e *Enrs) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(e)
}
// HashTreeRootWith ssz hashes the Enrs object with a hasher
func (e *Enrs) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Enrs'
{
subIndx := hh.Index()
num := uint64(len(e.Enrs))
if num > 32 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range e.Enrs {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Enrs object
func (e *Enrs) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(e)
}

View file

@ -1,957 +0,0 @@
// Code generated by fastssz. DO NOT EDIT.
// Hash: 26a61b12807ff78c64a029acdd5bcb580dfe35b7bfbf8bf04ceebae1a3d5cac1
// Version: 0.1.3
package portalwire
import (
ssz "github.com/ferranbt/fastssz"
)
// MarshalSSZ ssz marshals the PingPongCustomData object
func (p *PingPongCustomData) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(p)
}
// MarshalSSZTo ssz marshals the PingPongCustomData object to a target array
func (p *PingPongCustomData) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'Radius'
if size := len(p.Radius); size != 32 {
err = ssz.ErrBytesLengthFn("PingPongCustomData.Radius", size, 32)
return
}
dst = append(dst, p.Radius...)
return
}
// UnmarshalSSZ ssz unmarshals the PingPongCustomData object
func (p *PingPongCustomData) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 32 {
return ssz.ErrSize
}
// Field (0) 'Radius'
if cap(p.Radius) == 0 {
p.Radius = make([]byte, 0, len(buf[0:32]))
}
p.Radius = append(p.Radius, buf[0:32]...)
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the PingPongCustomData object
func (p *PingPongCustomData) SizeSSZ() (size int) {
size = 32
return
}
// HashTreeRoot ssz hashes the PingPongCustomData object
func (p *PingPongCustomData) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(p)
}
// HashTreeRootWith ssz hashes the PingPongCustomData object with a hasher
func (p *PingPongCustomData) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Radius'
if size := len(p.Radius); size != 32 {
err = ssz.ErrBytesLengthFn("PingPongCustomData.Radius", size, 32)
return
}
hh.PutBytes(p.Radius)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the PingPongCustomData object
func (p *PingPongCustomData) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(p)
}
// MarshalSSZ ssz marshals the Ping object
func (p *Ping) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(p)
}
// MarshalSSZTo ssz marshals the Ping object to a target array
func (p *Ping) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(12)
// Field (0) 'EnrSeq'
dst = ssz.MarshalUint64(dst, p.EnrSeq)
// Offset (1) 'CustomPayload'
dst = ssz.WriteOffset(dst, offset)
offset += len(p.CustomPayload)
// Field (1) 'CustomPayload'
if size := len(p.CustomPayload); size > 2048 {
err = ssz.ErrBytesLengthFn("Ping.CustomPayload", size, 2048)
return
}
dst = append(dst, p.CustomPayload...)
return
}
// UnmarshalSSZ ssz unmarshals the Ping object
func (p *Ping) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 12 {
return ssz.ErrSize
}
tail := buf
var o1 uint64
// Field (0) 'EnrSeq'
p.EnrSeq = ssz.UnmarshallUint64(buf[0:8])
// Offset (1) 'CustomPayload'
if o1 = ssz.ReadOffset(buf[8:12]); o1 > size {
return ssz.ErrOffset
}
if o1 < 12 {
return ssz.ErrInvalidVariableOffset
}
// Field (1) 'CustomPayload'
{
buf = tail[o1:]
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(p.CustomPayload) == 0 {
p.CustomPayload = make([]byte, 0, len(buf))
}
p.CustomPayload = append(p.CustomPayload, buf...)
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Ping object
func (p *Ping) SizeSSZ() (size int) {
size = 12
// Field (1) 'CustomPayload'
size += len(p.CustomPayload)
return
}
// HashTreeRoot ssz hashes the Ping object
func (p *Ping) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(p)
}
// HashTreeRootWith ssz hashes the Ping object with a hasher
func (p *Ping) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'EnrSeq'
hh.PutUint64(p.EnrSeq)
// Field (1) 'CustomPayload'
{
elemIndx := hh.Index()
byteLen := uint64(len(p.CustomPayload))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.Append(p.CustomPayload)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Ping object
func (p *Ping) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(p)
}
// MarshalSSZ ssz marshals the FindNodes object
func (f *FindNodes) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(f)
}
// MarshalSSZTo ssz marshals the FindNodes object to a target array
func (f *FindNodes) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(4)
// Offset (0) 'Distances'
dst = ssz.WriteOffset(dst, offset)
offset += len(f.Distances) * 2
// Field (0) 'Distances'
if size := len(f.Distances); size > 256 {
err = ssz.ErrListTooBigFn("FindNodes.Distances", size, 256)
return
}
for ii := 0; ii < len(f.Distances); ii++ {
dst = append(dst, f.Distances[ii][:]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the FindNodes object
func (f *FindNodes) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 4 {
return ssz.ErrSize
}
tail := buf
var o0 uint64
// Offset (0) 'Distances'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 < 4 {
return ssz.ErrInvalidVariableOffset
}
// Field (0) 'Distances'
{
buf = tail[o0:]
num, err := ssz.DivideInt2(len(buf), 2, 256)
if err != nil {
return err
}
f.Distances = make([][2]byte, num)
for ii := 0; ii < num; ii++ {
copy(f.Distances[ii][:], buf[ii*2:(ii+1)*2])
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the FindNodes object
func (f *FindNodes) SizeSSZ() (size int) {
size = 4
// Field (0) 'Distances'
size += len(f.Distances) * 2
return
}
// HashTreeRoot ssz hashes the FindNodes object
func (f *FindNodes) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(f)
}
// HashTreeRootWith ssz hashes the FindNodes object with a hasher
func (f *FindNodes) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Distances'
{
if size := len(f.Distances); size > 256 {
err = ssz.ErrListTooBigFn("FindNodes.Distances", size, 256)
return
}
subIndx := hh.Index()
for _, i := range f.Distances {
hh.PutBytes(i[:])
}
numItems := uint64(len(f.Distances))
hh.MerkleizeWithMixin(subIndx, numItems, 256)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the FindNodes object
func (f *FindNodes) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(f)
}
// MarshalSSZ ssz marshals the FindContent object
func (f *FindContent) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(f)
}
// MarshalSSZTo ssz marshals the FindContent object to a target array
func (f *FindContent) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(4)
// Offset (0) 'ContentKey'
dst = ssz.WriteOffset(dst, offset)
offset += len(f.ContentKey)
// Field (0) 'ContentKey'
if size := len(f.ContentKey); size > 2048 {
err = ssz.ErrBytesLengthFn("FindContent.ContentKey", size, 2048)
return
}
dst = append(dst, f.ContentKey...)
return
}
// UnmarshalSSZ ssz unmarshals the FindContent object
func (f *FindContent) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 4 {
return ssz.ErrSize
}
tail := buf
var o0 uint64
// Offset (0) 'ContentKey'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 < 4 {
return ssz.ErrInvalidVariableOffset
}
// Field (0) 'ContentKey'
{
buf = tail[o0:]
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(f.ContentKey) == 0 {
f.ContentKey = make([]byte, 0, len(buf))
}
f.ContentKey = append(f.ContentKey, buf...)
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the FindContent object
func (f *FindContent) SizeSSZ() (size int) {
size = 4
// Field (0) 'ContentKey'
size += len(f.ContentKey)
return
}
// HashTreeRoot ssz hashes the FindContent object
func (f *FindContent) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(f)
}
// HashTreeRootWith ssz hashes the FindContent object with a hasher
func (f *FindContent) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'ContentKey'
{
elemIndx := hh.Index()
byteLen := uint64(len(f.ContentKey))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.Append(f.ContentKey)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the FindContent object
func (f *FindContent) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(f)
}
// MarshalSSZ ssz marshals the Offer object
func (o *Offer) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(o)
}
// MarshalSSZTo ssz marshals the Offer object to a target array
func (o *Offer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(4)
// Offset (0) 'ContentKeys'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(o.ContentKeys); ii++ {
offset += 4
offset += len(o.ContentKeys[ii])
}
// Field (0) 'ContentKeys'
if size := len(o.ContentKeys); size > 64 {
err = ssz.ErrListTooBigFn("Offer.ContentKeys", size, 64)
return
}
{
offset = 4 * len(o.ContentKeys)
for ii := 0; ii < len(o.ContentKeys); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(o.ContentKeys[ii])
}
}
for ii := 0; ii < len(o.ContentKeys); ii++ {
if size := len(o.ContentKeys[ii]); size > 2048 {
err = ssz.ErrBytesLengthFn("Offer.ContentKeys[ii]", size, 2048)
return
}
dst = append(dst, o.ContentKeys[ii]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the Offer object
func (o *Offer) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 4 {
return ssz.ErrSize
}
tail := buf
var o0 uint64
// Offset (0) 'ContentKeys'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 < 4 {
return ssz.ErrInvalidVariableOffset
}
// Field (0) 'ContentKeys'
{
buf = tail[o0:]
num, err := ssz.DecodeDynamicLength(buf, 64)
if err != nil {
return err
}
o.ContentKeys = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(o.ContentKeys[indx]) == 0 {
o.ContentKeys[indx] = make([]byte, 0, len(buf))
}
o.ContentKeys[indx] = append(o.ContentKeys[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Offer object
func (o *Offer) SizeSSZ() (size int) {
size = 4
// Field (0) 'ContentKeys'
for ii := 0; ii < len(o.ContentKeys); ii++ {
size += 4
size += len(o.ContentKeys[ii])
}
return
}
// HashTreeRoot ssz hashes the Offer object
func (o *Offer) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(o)
}
// HashTreeRootWith ssz hashes the Offer object with a hasher
func (o *Offer) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'ContentKeys'
{
subIndx := hh.Index()
num := uint64(len(o.ContentKeys))
if num > 64 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range o.ContentKeys {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 64)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Offer object
func (o *Offer) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(o)
}
// MarshalSSZ ssz marshals the Pong object
func (p *Pong) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(p)
}
// MarshalSSZTo ssz marshals the Pong object to a target array
func (p *Pong) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(12)
// Field (0) 'EnrSeq'
dst = ssz.MarshalUint64(dst, p.EnrSeq)
// Offset (1) 'CustomPayload'
dst = ssz.WriteOffset(dst, offset)
offset += len(p.CustomPayload)
// Field (1) 'CustomPayload'
if size := len(p.CustomPayload); size > 2048 {
err = ssz.ErrBytesLengthFn("Pong.CustomPayload", size, 2048)
return
}
dst = append(dst, p.CustomPayload...)
return
}
// UnmarshalSSZ ssz unmarshals the Pong object
func (p *Pong) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 12 {
return ssz.ErrSize
}
tail := buf
var o1 uint64
// Field (0) 'EnrSeq'
p.EnrSeq = ssz.UnmarshallUint64(buf[0:8])
// Offset (1) 'CustomPayload'
if o1 = ssz.ReadOffset(buf[8:12]); o1 > size {
return ssz.ErrOffset
}
if o1 < 12 {
return ssz.ErrInvalidVariableOffset
}
// Field (1) 'CustomPayload'
{
buf = tail[o1:]
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(p.CustomPayload) == 0 {
p.CustomPayload = make([]byte, 0, len(buf))
}
p.CustomPayload = append(p.CustomPayload, buf...)
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Pong object
func (p *Pong) SizeSSZ() (size int) {
size = 12
// Field (1) 'CustomPayload'
size += len(p.CustomPayload)
return
}
// HashTreeRoot ssz hashes the Pong object
func (p *Pong) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(p)
}
// HashTreeRootWith ssz hashes the Pong object with a hasher
func (p *Pong) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'EnrSeq'
hh.PutUint64(p.EnrSeq)
// Field (1) 'CustomPayload'
{
elemIndx := hh.Index()
byteLen := uint64(len(p.CustomPayload))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.Append(p.CustomPayload)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Pong object
func (p *Pong) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(p)
}
// MarshalSSZ ssz marshals the Nodes object
func (n *Nodes) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(n)
}
// MarshalSSZTo ssz marshals the Nodes object to a target array
func (n *Nodes) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(5)
// Field (0) 'Total'
dst = ssz.MarshalUint8(dst, n.Total)
// Offset (1) 'Enrs'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(n.Enrs); ii++ {
offset += 4
offset += len(n.Enrs[ii])
}
// Field (1) 'Enrs'
if size := len(n.Enrs); size > 32 {
err = ssz.ErrListTooBigFn("Nodes.Enrs", size, 32)
return
}
{
offset = 4 * len(n.Enrs)
for ii := 0; ii < len(n.Enrs); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(n.Enrs[ii])
}
}
for ii := 0; ii < len(n.Enrs); ii++ {
if size := len(n.Enrs[ii]); size > 2048 {
err = ssz.ErrBytesLengthFn("Nodes.Enrs[ii]", size, 2048)
return
}
dst = append(dst, n.Enrs[ii]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the Nodes object
func (n *Nodes) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 5 {
return ssz.ErrSize
}
tail := buf
var o1 uint64
// Field (0) 'Total'
n.Total = ssz.UnmarshallUint8(buf[0:1])
// Offset (1) 'Enrs'
if o1 = ssz.ReadOffset(buf[1:5]); o1 > size {
return ssz.ErrOffset
}
if o1 < 5 {
return ssz.ErrInvalidVariableOffset
}
// Field (1) 'Enrs'
{
buf = tail[o1:]
num, err := ssz.DecodeDynamicLength(buf, 32)
if err != nil {
return err
}
n.Enrs = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 2048 {
return ssz.ErrBytesLength
}
if cap(n.Enrs[indx]) == 0 {
n.Enrs[indx] = make([]byte, 0, len(buf))
}
n.Enrs[indx] = append(n.Enrs[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Nodes object
func (n *Nodes) SizeSSZ() (size int) {
size = 5
// Field (1) 'Enrs'
for ii := 0; ii < len(n.Enrs); ii++ {
size += 4
size += len(n.Enrs[ii])
}
return
}
// HashTreeRoot ssz hashes the Nodes object
func (n *Nodes) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(n)
}
// HashTreeRootWith ssz hashes the Nodes object with a hasher
func (n *Nodes) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Total'
hh.PutUint8(n.Total)
// Field (1) 'Enrs'
{
subIndx := hh.Index()
num := uint64(len(n.Enrs))
if num > 32 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range n.Enrs {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 2048 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (2048+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 32)
}
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Nodes object
func (n *Nodes) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(n)
}
// MarshalSSZ ssz marshals the ConnectionId object
func (c *ConnectionId) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(c)
}
// MarshalSSZTo ssz marshals the ConnectionId object to a target array
func (c *ConnectionId) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'Id'
if size := len(c.Id); size != 2 {
err = ssz.ErrBytesLengthFn("ConnectionId.Id", size, 2)
return
}
dst = append(dst, c.Id...)
return
}
// UnmarshalSSZ ssz unmarshals the ConnectionId object
func (c *ConnectionId) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size != 2 {
return ssz.ErrSize
}
// Field (0) 'Id'
if cap(c.Id) == 0 {
c.Id = make([]byte, 0, len(buf[0:2]))
}
c.Id = append(c.Id, buf[0:2]...)
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the ConnectionId object
func (c *ConnectionId) SizeSSZ() (size int) {
size = 2
return
}
// HashTreeRoot ssz hashes the ConnectionId object
func (c *ConnectionId) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(c)
}
// HashTreeRootWith ssz hashes the ConnectionId object with a hasher
func (c *ConnectionId) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Id'
if size := len(c.Id); size != 2 {
err = ssz.ErrBytesLengthFn("ConnectionId.Id", size, 2)
return
}
hh.PutBytes(c.Id)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the ConnectionId object
func (c *ConnectionId) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(c)
}
// MarshalSSZ ssz marshals the Accept object
func (a *Accept) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(a)
}
// MarshalSSZTo ssz marshals the Accept object to a target array
func (a *Accept) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(6)
// Field (0) 'ConnectionId'
if size := len(a.ConnectionId); size != 2 {
err = ssz.ErrBytesLengthFn("Accept.ConnectionId", size, 2)
return
}
dst = append(dst, a.ConnectionId...)
// Offset (1) 'ContentKeys'
dst = ssz.WriteOffset(dst, offset)
offset += len(a.ContentKeys)
// Field (1) 'ContentKeys'
if size := len(a.ContentKeys); size > 64 {
err = ssz.ErrBytesLengthFn("Accept.ContentKeys", size, 64)
return
}
dst = append(dst, a.ContentKeys...)
return
}
// UnmarshalSSZ ssz unmarshals the Accept object
func (a *Accept) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 6 {
return ssz.ErrSize
}
tail := buf
var o1 uint64
// Field (0) 'ConnectionId'
if cap(a.ConnectionId) == 0 {
a.ConnectionId = make([]byte, 0, len(buf[0:2]))
}
a.ConnectionId = append(a.ConnectionId, buf[0:2]...)
// Offset (1) 'ContentKeys'
if o1 = ssz.ReadOffset(buf[2:6]); o1 > size {
return ssz.ErrOffset
}
if o1 < 6 {
return ssz.ErrInvalidVariableOffset
}
// Field (1) 'ContentKeys'
{
buf = tail[o1:]
if err = ssz.ValidateBitlist(buf, 64); err != nil {
return err
}
if cap(a.ContentKeys) == 0 {
a.ContentKeys = make([]byte, 0, len(buf))
}
a.ContentKeys = append(a.ContentKeys, buf...)
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the Accept object
func (a *Accept) SizeSSZ() (size int) {
size = 6
// Field (1) 'ContentKeys'
size += len(a.ContentKeys)
return
}
// HashTreeRoot ssz hashes the Accept object
func (a *Accept) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(a)
}
// HashTreeRootWith ssz hashes the Accept object with a hasher
func (a *Accept) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'ConnectionId'
if size := len(a.ConnectionId); size != 2 {
err = ssz.ErrBytesLengthFn("Accept.ConnectionId", size, 2)
return
}
hh.PutBytes(a.ConnectionId)
// Field (1) 'ContentKeys'
if len(a.ContentKeys) == 0 {
err = ssz.ErrEmptyBitlist
return
}
hh.PutBitlist(a.ContentKeys, 64)
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Accept object
func (a *Accept) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(a)
}

View file

@ -1,212 +0,0 @@
package portalwire
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
ssz "github.com/ferranbt/fastssz"
"github.com/holiman/uint256"
"github.com/prysmaticlabs/go-bitfield"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var maxUint256 = uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
// https://github.com/ethereum/portal-network-specs/blob/master/portal-wire-test-vectors.md
// we remove the message type here
func TestPingMessage(t *testing.T) {
dataRadius := maxUint256.Sub(maxUint256, uint256.NewInt(1))
reverseBytes, err := dataRadius.MarshalSSZ()
require.NoError(t, err)
customData := &PingPongCustomData{
Radius: reverseBytes,
}
dataBytes, err := customData.MarshalSSZ()
assert.NoError(t, err)
ping := &Ping{
EnrSeq: 1,
CustomPayload: dataBytes,
}
expected := "0x01000000000000000c000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
data, err := ping.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestPongMessage(t *testing.T) {
dataRadius := maxUint256.Div(maxUint256, uint256.NewInt(2))
reverseBytes, err := dataRadius.MarshalSSZ()
require.NoError(t, err)
customData := &PingPongCustomData{
Radius: reverseBytes,
}
dataBytes, err := customData.MarshalSSZ()
assert.NoError(t, err)
pong := &Pong{
EnrSeq: 1,
CustomPayload: dataBytes,
}
expected := "0x01000000000000000c000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
data, err := pong.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestFindNodesMessage(t *testing.T) {
distances := []uint16{256, 255}
distancesBytes := make([][2]byte, len(distances))
for i, distance := range distances {
copy(distancesBytes[i][:], ssz.MarshalUint16(make([]byte, 0), distance))
}
findNode := &FindNodes{
Distances: distancesBytes,
}
data, err := findNode.MarshalSSZ()
expected := "0x040000000001ff00"
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}
func TestNodes(t *testing.T) {
enrs := []string{
"enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg",
"enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU",
}
enrsBytes := make([][]byte, 0)
for _, enr := range enrs {
n, err := enode.Parse(enode.ValidSchemes, enr)
assert.NoError(t, err)
enrBytes, err := rlp.EncodeToBytes(n.Record())
assert.NoError(t, err)
enrsBytes = append(enrsBytes, enrBytes)
}
testCases := []struct {
name string
input [][]byte
expected string
}{
{
name: "empty nodes",
input: make([][]byte, 0),
expected: "0x0105000000",
},
{
name: "two nodes",
input: enrsBytes,
expected: "0x0105000000080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
nodes := &Nodes{
Total: 1,
Enrs: test.input,
}
data, err := nodes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, test.expected, fmt.Sprintf("0x%x", data))
})
}
}
func TestContent(t *testing.T) {
contentKey := "0x706f7274616c"
content := &FindContent{
ContentKey: hexutil.MustDecode(contentKey),
}
expected := "0x04000000706f7274616c"
data, err := content.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expected = "0x7468652063616b652069732061206c6965"
contentRes := &Content{
Content: hexutil.MustDecode("0x7468652063616b652069732061206c6965"),
}
data, err = contentRes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expectData := &Content{}
err = expectData.UnmarshalSSZ(data)
assert.NoError(t, err)
assert.Equal(t, contentRes.Content, expectData.Content)
enrs := []string{
"enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg",
"enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU",
}
enrsBytes := make([][]byte, 0)
for _, enr := range enrs {
n, err := enode.Parse(enode.ValidSchemes, enr)
assert.NoError(t, err)
enrBytes, err := rlp.EncodeToBytes(n.Record())
assert.NoError(t, err)
enrsBytes = append(enrsBytes, enrBytes)
}
enrsRes := &Enrs{
Enrs: enrsBytes,
}
expected = "0x080000007f000000f875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
data, err = enrsRes.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
expectEnrs := &Enrs{}
err = expectEnrs.UnmarshalSSZ(data)
assert.NoError(t, err)
assert.Equal(t, expectEnrs.Enrs, enrsRes.Enrs)
}
func TestOfferAndAcceptMessage(t *testing.T) {
contentKey := "0x010203"
contentBytes := hexutil.MustDecode(contentKey)
contentKeys := [][]byte{contentBytes}
offer := &Offer{
ContentKeys: contentKeys,
}
expected := "0x0400000004000000010203"
data, err := offer.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
contentKeyBitlist := bitfield.NewBitlist(8)
contentKeyBitlist.SetBitAt(0, true)
accept := &Accept{
ConnectionId: []byte{0x01, 0x02},
ContentKeys: contentKeyBitlist,
}
expected = "0x0102060000000101"
data, err = accept.MarshalSSZ()
assert.NoError(t, err)
assert.Equal(t, expected, fmt.Sprintf("0x%x", data))
}

View file

@ -1,75 +0,0 @@
package state
import (
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
)
type API struct {
*portalwire.PortalProtocolAPI
}
func (p *API) StateRoutingTableInfo() *portalwire.RoutingTableInfo {
return p.RoutingTableInfo()
}
func (p *API) StateAddEnr(enr string) (bool, error) {
return p.AddEnr(enr)
}
func (p *API) StateGetEnr(nodeId string) (string, error) {
return p.GetEnr(nodeId)
}
func (p *API) StateDeleteEnr(nodeId string) (bool, error) {
return p.DeleteEnr(nodeId)
}
func (p *API) StateLookupEnr(nodeId string) (string, error) {
return p.LookupEnr(nodeId)
}
func (p *API) StatePing(enr string) (*portalwire.PortalPongResp, error) {
return p.Ping(enr)
}
func (p *API) StateFindNodes(enr string, distances []uint) ([]string, error) {
return p.FindNodes(enr, distances)
}
func (p *API) StateFindContent(enr string, contentKey string) (interface{}, error) {
return p.FindContent(enr, contentKey)
}
func (p *API) StateOffer(enr string, contentItems [][2]string) (string, error) {
return p.Offer(enr, contentItems)
}
func (p *API) StateRecursiveFindNodes(nodeId string) ([]string, error) {
return p.RecursiveFindNodes(nodeId)
}
func (p *API) StateGetContent(contentKeyHex string) (*portalwire.ContentInfo, error) {
return p.RecursiveFindContent(contentKeyHex)
}
func (p *API) StateLocalContent(contentKeyHex string) (string, error) {
return p.LocalContent(contentKeyHex)
}
func (p *API) StateStore(contentKeyHex string, contextHex string) (bool, error) {
return p.Store(contentKeyHex, contextHex)
}
func (p *API) StateGossip(contentKeyHex, contentHex string) (int, error) {
return p.Gossip(contentKeyHex, contentHex)
}
func (p *API) StateTraceGetContent(contentKeyHex string) (*portalwire.TraceContentResult, error) {
return p.TraceRecursiveFindContent(contentKeyHex)
}
func NewStateNetworkAPI(portalProtocolAPI *portalwire.PortalProtocolAPI) *API {
return &API{
portalProtocolAPI,
}
}

View file

@ -1,296 +0,0 @@
package state
import (
"bytes"
"context"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/portalnetwork/history"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/codec"
)
type StateNetwork struct {
portalProtocol *portalwire.PortalProtocol
closeCtx context.Context
closeFunc context.CancelFunc
log log.Logger
spec *common.Spec
client *rpc.Client
}
func NewStateNetwork(portalProtocol *portalwire.PortalProtocol, client *rpc.Client) *StateNetwork {
ctx, cancel := context.WithCancel(context.Background())
return &StateNetwork{
portalProtocol: portalProtocol,
closeCtx: ctx,
closeFunc: cancel,
log: log.New("sub-protocol", "state"),
spec: configs.Mainnet,
client: client,
}
}
func (h *StateNetwork) Start() error {
err := h.portalProtocol.Start()
if err != nil {
return err
}
go h.processContentLoop(h.closeCtx)
h.log.Debug("state network start successfully")
return nil
}
func (h *StateNetwork) Stop() {
h.closeFunc()
h.portalProtocol.Stop()
}
func (h *StateNetwork) processContentLoop(ctx context.Context) {
contentChan := h.portalProtocol.GetContent()
for {
select {
case <-ctx.Done():
return
case contentElement := <-contentChan:
err := h.validateContents(contentElement.ContentKeys, contentElement.Contents)
if err != nil {
h.log.Error("validate content failed", "err", err)
continue
}
go func(ctx context.Context) {
select {
case <-ctx.Done():
return
default:
var gossippedNum int
gossippedNum, err = h.portalProtocol.Gossip(&contentElement.Node, contentElement.ContentKeys, contentElement.Contents)
h.log.Trace("gossippedNum", "gossippedNum", gossippedNum)
if err != nil {
h.log.Error("gossip failed", "err", err)
return
}
}
}(ctx)
}
}
}
func (h *StateNetwork) validateContents(contentKeys [][]byte, contents [][]byte) error {
for i, content := range contents {
contentKey := contentKeys[i]
err := h.validateContent(contentKey, content)
if err != nil {
h.log.Error("content validate failed", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content), "err", err)
return fmt.Errorf("content validate failed with content key %x and content %x", contentKey, content)
}
contentId := h.portalProtocol.ToContentId(contentKey)
err = h.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
return err
}
}
return nil
}
func (h *StateNetwork) validateContent(contentKey []byte, content []byte) error {
keyType := contentKey[0]
switch keyType {
case AccountTrieNodeType:
return h.validateAccountTrieNode(contentKey[1:], content)
case ContractStorageTrieNodeType:
return h.validateContractStorageTrieNode(contentKey[1:], content)
case ContractByteCodeType:
return h.validateContractByteCode(contentKey[1:], content)
}
return errors.New("unknown content type")
}
func (h *StateNetwork) validateAccountTrieNode(contentKey []byte, content []byte) error {
accountKey := &AccountTrieNodeKey{}
err := accountKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
accountData := &AccountTrieNodeWithProof{}
err = accountData.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
// get HeaderWithProof in history network
stateRoot, err := h.getStateRoot(accountData.BlockHash)
if err != nil {
return err
}
err = validateNodeTrieProof(stateRoot, accountKey.NodeHash, &accountKey.Path, &accountData.Proof)
return err
}
func (h *StateNetwork) validateContractStorageTrieNode(contentKey []byte, content []byte) error {
contractStorageKey := &ContractStorageTrieNodeKey{}
err := contractStorageKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
contractProof := &ContractStorageTrieNodeWithProof{}
err = contractProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
stateRoot, err := h.getStateRoot(contractProof.BlockHash)
if err != nil {
return err
}
accountState, err := validateAccountState(stateRoot, contractStorageKey.AddressHash, &contractProof.AccountProof)
if err != nil {
return err
}
err = validateNodeTrieProof(common.Bytes32(accountState.Root), contractStorageKey.NodeHash, &contractStorageKey.Path, &contractProof.StoregeProof)
return err
}
func (h *StateNetwork) validateContractByteCode(contentKey []byte, content []byte) error {
contractByteCodeKey := &ContractBytecodeKey{}
err := contractByteCodeKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
contractBytecodeWithProof := &ContractBytecodeWithProof{}
err = contractBytecodeWithProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
stateRoot, err := h.getStateRoot(contractBytecodeWithProof.BlockHash)
if err != nil {
return err
}
accountState, err := validateAccountState(stateRoot, contractByteCodeKey.AddressHash, &contractBytecodeWithProof.AccountProof)
if err != nil {
return err
}
if !bytes.Equal(accountState.CodeHash, contractByteCodeKey.CodeHash[:]) {
return errors.New("account state is invalid")
}
return nil
}
func (h *StateNetwork) getStateRoot(blockHash common.Bytes32) (common.Bytes32, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
contentKey := make([]byte, 0)
contentKey = append(contentKey, byte(history.BlockHeaderType))
contentKey = append(contentKey, blockHash[:]...)
arg := hexutil.Encode(contentKey)
res := &portalwire.ContentInfo{}
err := h.client.CallContext(ctx, res, "portal_historyGetContent", arg)
if err != nil {
return common.Bytes32{}, err
}
data, err := hexutil.Decode(res.Content)
if err != nil {
return common.Bytes32{}, err
}
headerWithProof, err := history.DecodeBlockHeaderWithProof(data)
if err != nil {
return common.Bytes32{}, err
}
header := new(types.Header)
err = rlp.DecodeBytes(headerWithProof.Header, header)
if err != nil {
return common.Bytes32{}, err
}
return common.Bytes32(header.Root), nil
}
func validateNodeTrieProof(rootHash common.Bytes32, nodeHash common.Bytes32, path *Nibbles, proof *TrieProof) error {
lastNode, p, err := validateTrieProof(rootHash, path.Nibbles, proof)
if err != nil {
return err
}
if len(p) != 0 {
return errors.New("path is too long")
}
err = checkNodeHash(&lastNode, nodeHash[:])
if err != nil {
return err
}
return nil
}
func validateAccountState(rootHash common.Bytes32, addrrssHash common.Bytes32, proof *TrieProof) (*types.StateAccount, error) {
path := make([]byte, 0, len(addrrssHash)*2)
for _, item := range addrrssHash {
before, after := unpackNibblePair(item)
path = append(path, before, after)
}
lastProof, p, err := validateTrieProof(rootHash, path, proof)
if err != nil {
return nil, err
}
n, err := trie.DecodeTrieNode(nil, lastProof)
if err != nil {
return nil, err
}
stateBytes, _, err := trie.TraverseTrieNode(n, p)
if err != nil {
return nil, err
}
return types.FullAccount(stateBytes)
}
func validateTrieProof(rootHash common.Bytes32, path []byte, proof *TrieProof) (EncodedTrieNode, []byte, error) {
if len(*proof) == 0 {
return nil, nil, errors.New("proof should be empty")
}
firstNode := []EncodedTrieNode(*proof)[0]
err := checkNodeHash(&firstNode, rootHash[:])
if err != nil {
return nil, nil, err
}
node := firstNode
remainingPath := path
for _, nextNode := range []EncodedTrieNode(*proof)[1:] {
n, err := trie.DecodeTrieNode(nil, node)
if err != nil {
return nil, nil, err
}
hashNode, p, err := trie.TraverseTrieNode(n, remainingPath)
if err != nil {
return nil, nil, err
}
err = checkNodeHash(&nextNode, hashNode)
if err != nil {
return nil, nil, err
}
node = nextNode
remainingPath = p
}
return node, remainingPath, nil
}
func checkNodeHash(node *EncodedTrieNode, hash []byte) error {
nodeHash := node.NodeHash()
if !bytes.Equal(nodeHash[:], hash[:]) {
return fmt.Errorf("node hash is not equal, expect: %v, actual: %v", hash, nodeHash)
}
return nil
}

View file

@ -1,107 +0,0 @@
package state
import (
"fmt"
"os"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/portalnetwork/history"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
type TestCase struct {
BlockHeader string `yaml:"block_header"`
ContentKey string `yaml:"content_key"`
ContentValueOffer string `yaml:"content_value_offer"`
ContentValueRetrieval string `yaml:"content_value_retrieval"`
}
func getTestCases(filename string) ([]TestCase, error) {
file, err := os.ReadFile(fmt.Sprintf("./testdata/%s", filename))
if err != nil {
return nil, err
}
res := make([]TestCase, 0)
err = yaml.Unmarshal(file, &res)
if err != nil {
return nil, err
}
return res, nil
}
type MockAPI struct {
header string
}
func (p *MockAPI) HistoryGetContent(contentKeyHex string) (*portalwire.ContentInfo, error) {
headerWithProof := &history.BlockHeaderWithProof{
Header: hexutil.MustDecode(p.header),
Proof: &history.BlockHeaderProof{
Selector: 0,
Proof: [][]byte{},
},
}
data, err := headerWithProof.MarshalSSZ()
if err != nil {
return nil, err
}
return &portalwire.ContentInfo{
Content: hexutil.Encode(data),
UtpTransfer: false,
}, nil
}
func TestValidateAccountTrieNode(t *testing.T) {
cases, err := getTestCases("account_trie_node.yaml")
require.NoError(t, err)
for _, tt := range cases {
server := rpc.NewServer()
api := &MockAPI{
header: tt.BlockHeader,
}
server.RegisterName("portal", api)
client := rpc.DialInProc(server)
bn := NewStateNetwork(nil, client)
err = bn.validateContent(hexutil.MustDecode(tt.ContentKey), hexutil.MustDecode(tt.ContentValueOffer))
require.NoError(t, err)
}
}
func TestValidateContractStorage(t *testing.T) {
cases, err := getTestCases("contract_storage_trie_node.yaml")
require.NoError(t, err)
for _, tt := range cases {
server := rpc.NewServer()
api := &MockAPI{
header: tt.BlockHeader,
}
server.RegisterName("portal", api)
client := rpc.DialInProc(server)
bn := NewStateNetwork(nil, client)
err = bn.validateContent(hexutil.MustDecode(tt.ContentKey), hexutil.MustDecode(tt.ContentValueOffer))
require.NoError(t, err)
}
}
func TestValidateContractByte(t *testing.T) {
cases, err := getTestCases("contract_bytecode.yaml")
require.NoError(t, err)
for _, tt := range cases {
server := rpc.NewServer()
api := &MockAPI{
header: tt.BlockHeader,
}
server.RegisterName("portal", api)
client := rpc.DialInProc(server)
bn := NewStateNetwork(nil, client)
err = bn.validateContent(hexutil.MustDecode(tt.ContentKey), hexutil.MustDecode(tt.ContentValueOffer))
require.NoError(t, err)
}
}

View file

@ -1,177 +0,0 @@
package state
import (
"bytes"
"crypto/sha256"
"database/sql"
"errors"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/portalnetwork/portalwire"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
"github.com/protolambda/ztyp/codec"
)
func defaultContentIdFunc(contentKey []byte) []byte {
digest := sha256.Sum256(contentKey)
return digest[:]
}
var _ storage.ContentStorage = &StateStorage{}
type StateStorage struct {
store storage.ContentStorage
db *sql.DB
log log.Logger
}
var portalStorageMetrics *portalwire.PortalStorageMetrics
func NewStateStorage(store storage.ContentStorage, db *sql.DB) *StateStorage {
storage := &StateStorage{
store: store,
db: db,
log: log.New("storage", "state"),
}
var err error
portalStorageMetrics, err = portalwire.NewPortalStorageMetrics("state", db)
if err != nil {
return nil
}
return storage
}
// Get implements storage.ContentStorage.
func (s *StateStorage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
return s.store.Get(contentKey, contentId)
}
// Put implements storage.ContentStorage.
func (s *StateStorage) Put(contentKey []byte, contentId []byte, content []byte) error {
keyType := contentKey[0]
switch keyType {
case AccountTrieNodeType:
return s.putAccountTrieNode(contentKey[1:], contentId, content)
case ContractStorageTrieNodeType:
return s.putContractStorageTrieNode(contentKey[1:], contentId, content)
case ContractByteCodeType:
return s.putContractBytecode(contentKey[1:], contentId, content)
}
return errors.New("unknown content type")
}
// Radius implements storage.ContentStorage.
func (s *StateStorage) Radius() *uint256.Int {
return s.store.Radius()
}
func (s *StateStorage) putAccountTrieNode(contentKey []byte, contentId []byte, content []byte) error {
accountKey := &AccountTrieNodeKey{}
err := accountKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
accountData := &AccountTrieNodeWithProof{}
err = accountData.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
length := len(accountData.Proof)
lastProof := accountData.Proof[length-1]
lastNodeHash := crypto.Keccak256(lastProof)
if !bytes.Equal(lastNodeHash, accountKey.NodeHash[:]) {
return errors.New("hash of the trie node doesn't match key's node_hash")
}
lastTrieNode := &TrieNode{
Node: lastProof,
}
var contentValueBuf bytes.Buffer
err = lastTrieNode.Serialize(codec.NewEncodingWriter(&contentValueBuf))
if err != nil {
return err
}
err = s.store.Put(contentId, contentId, contentValueBuf.Bytes())
if err != nil {
s.log.Error("failed to save data after validate", "type", contentKey[0], "key", contentKey[1:], "value", content)
} else if metrics.Enabled {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(content)))
}
return nil
}
func (s *StateStorage) putContractStorageTrieNode(contentKey []byte, contentId []byte, content []byte) error {
contractStorageKey := &ContractStorageTrieNodeKey{}
err := contractStorageKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
contractProof := &ContractStorageTrieNodeWithProof{}
err = contractProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
length := len(contractProof.StoregeProof)
lastProof := contractProof.StoregeProof[length-1]
lastNodeHash := crypto.Keccak256(lastProof)
if !bytes.Equal(lastNodeHash, contractStorageKey.NodeHash[:]) {
return errors.New("hash of the contract storage node doesn't match key's node hash")
}
lastTrieNode := &TrieNode{
Node: lastProof,
}
var contentValueBuf bytes.Buffer
err = lastTrieNode.Serialize(codec.NewEncodingWriter(&contentValueBuf))
if err != nil {
return err
}
err = s.store.Put(contentId, contentId, contentValueBuf.Bytes())
if err != nil {
s.log.Error("failed to save data after validate", "type", contentKey[0], "key", contentKey[1:], "value", content)
} else if metrics.Enabled {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(content)))
}
return nil
}
func (s *StateStorage) putContractBytecode(contentKey []byte, contentId []byte, content []byte) error {
contractByteCodeKey := &ContractBytecodeKey{}
err := contractByteCodeKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
if err != nil {
return err
}
contractBytecodeWithProof := &ContractBytecodeWithProof{}
err = contractBytecodeWithProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
if err != nil {
return err
}
codeHash := crypto.Keccak256(contractBytecodeWithProof.Code)
if !bytes.Equal(codeHash, contractByteCodeKey.CodeHash[:]) {
return errors.New("hash of the contract byte doesn't match key's code hash")
}
container := &ContractBytecodeContainer{
Code: contractBytecodeWithProof.Code,
}
var contentValueBuf bytes.Buffer
err = container.Serialize(codec.NewEncodingWriter(&contentValueBuf))
if err != nil {
return err
}
err = s.store.Put(contentId, contentId, contentValueBuf.Bytes())
if err != nil {
s.log.Error("failed to save data after validate", "type", contentKey[0], "key", contentKey[1:], "value", content)
} else if metrics.Enabled {
portalStorageMetrics.EntriesCount.Inc(1)
portalStorageMetrics.ContentStorageUsage.Inc(int64(len(content)))
}
return nil
}

View file

@ -1,28 +0,0 @@
package state
import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/stretchr/testify/require"
)
func TestStorage(t *testing.T) {
storage := storage.NewMockStorage()
stateStorage := NewStateStorage(storage, nil)
testfiles := []string{"account_trie_node.yaml", "contract_storage_trie_node.yaml", "contract_bytecode.yaml"}
for _, file := range testfiles {
cases, err := getTestCases(file)
require.NoError(t, err)
for _, tt := range cases {
contentKey := hexutil.MustDecode(tt.ContentKey)
contentId := defaultContentIdFunc(contentKey)
err = stateStorage.Put(contentKey, contentId, hexutil.MustDecode(tt.ContentValueOffer))
require.NoError(t, err)
res, err := stateStorage.Get(contentKey, contentId)
require.NoError(t, err)
require.Equal(t, hexutil.MustDecode(tt.ContentValueRetrieval), res)
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,515 +0,0 @@
package state
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/crypto"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
const (
AccountTrieNodeType byte = 0x20
ContractStorageTrieNodeType byte = 0x21
ContractByteCodeType byte = 0x22
)
var _ common.SSZObj = (*Nibbles)(nil)
type Nibbles struct {
Nibbles []byte
}
func (n *Nibbles) Serialize(w *codec.EncodingWriter) error {
if len(n.Nibbles)%2 == 0 {
err := w.WriteByte(0)
if err != nil {
return err
}
for i := 0; i < len(n.Nibbles); i += 2 {
err = w.WriteByte(n.Nibbles[i]<<4 | n.Nibbles[i+1])
if err != nil {
return err
}
}
} else {
err := w.WriteByte(0x10 | n.Nibbles[0])
if err != nil {
return err
}
for i := 1; i < len(n.Nibbles); i += 2 {
err = w.WriteByte(n.Nibbles[i]<<4 | n.Nibbles[i+1])
if err != nil {
return err
}
}
}
return nil
}
func (n *Nibbles) ByteLength() uint64 {
return uint64(len(n.Nibbles)/2 + 1)
}
func (n *Nibbles) FixedLength() uint64 {
return 0
}
func (n *Nibbles) Deserialize(dr *codec.DecodingReader) error {
firstByte, err := dr.ReadByte()
if err != nil {
return err
}
packedNibbles := make([]byte, dr.Scope())
_, err = dr.Read(packedNibbles)
if err != nil {
return err
}
flag, first := unpackNibblePair(firstByte)
nibbles := make([]byte, 0, 1+2*len(packedNibbles))
if flag == 0 {
if first != 0 {
return fmt.Errorf("nibbles: The lowest 4 bits of the first byte must be 0, but was: %x", first)
}
} else if flag == 1 {
nibbles = append(nibbles, first)
} else {
return fmt.Errorf("nibbles: The highest 4 bits of the first byte must be 0 or 1, but was: %x", flag)
}
for _, b := range packedNibbles {
left, right := unpackNibblePair(b)
nibbles = append(nibbles, left, right)
}
unpackedNibbles, err := FromUnpackedNibbles(nibbles)
if err != nil {
return err
}
*n = *unpackedNibbles
return nil
}
func (n *Nibbles) HashTreeRoot(h tree.HashFn) tree.Root {
//TODO implement me
panic("implement me")
}
func FromUnpackedNibbles(nibbles []byte) (*Nibbles, error) {
if len(nibbles) > 64 {
return nil, errors.New("too many nibbles")
}
for _, nibble := range nibbles {
if nibble > 0xf {
return nil, errors.New("nibble out of range")
}
}
return &Nibbles{Nibbles: nibbles}, nil
}
func unpackNibblePair(pair byte) (byte, byte) {
return pair >> 4, pair & 0xf
}
// test data from
// https://github.com/ethereum/portal-network-specs/blob/master/state/state-network-test-vectors.md
type AccountTrieNodeKey struct {
Path Nibbles
NodeHash common.Bytes32
}
func (a *AccountTrieNodeKey) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&a.Path,
&a.NodeHash,
)
}
func (a *AccountTrieNodeKey) Serialize(w *codec.EncodingWriter) error {
return w.Container(
&a.Path,
&a.NodeHash,
)
}
func (a *AccountTrieNodeKey) ByteLength(spec *common.Spec) uint64 {
return codec.ContainerLength(
&a.Path,
&a.NodeHash,
)
}
func (a *AccountTrieNodeKey) FixedLength(spec *common.Spec) uint64 {
return 0
}
func (a *AccountTrieNodeKey) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&a.Path,
&a.NodeHash,
)
}
type ContractStorageTrieNodeKey struct {
AddressHash common.Bytes32
Path Nibbles
NodeHash common.Bytes32
}
func (c *ContractStorageTrieNodeKey) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&c.AddressHash,
&c.Path,
&c.NodeHash,
)
}
func (c *ContractStorageTrieNodeKey) Serialize(w *codec.EncodingWriter) error {
return w.Container(
&c.AddressHash,
&c.Path,
&c.NodeHash,
)
}
func (c *ContractStorageTrieNodeKey) ByteLength(spec *common.Spec) uint64 {
return codec.ContainerLength(
&c.AddressHash,
&c.Path,
&c.NodeHash,
)
}
func (c *ContractStorageTrieNodeKey) FixedLength(spec *common.Spec) uint64 {
return 0
}
func (c *ContractStorageTrieNodeKey) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&c.AddressHash,
&c.Path,
&c.NodeHash,
)
}
type ContractBytecodeKey struct {
AddressHash common.Bytes32
CodeHash common.Bytes32
}
func (c *ContractBytecodeKey) Deserialize(dr *codec.DecodingReader) error {
return dr.FixedLenContainer(
&c.AddressHash,
&c.CodeHash,
)
}
func (c *ContractBytecodeKey) Serialize(w *codec.EncodingWriter) error {
return w.FixedLenContainer(
&c.AddressHash,
&c.CodeHash,
)
}
func (c *ContractBytecodeKey) ByteLength(spec *common.Spec) uint64 {
return codec.ContainerLength(
&c.AddressHash,
&c.CodeHash,
)
}
func (c *ContractBytecodeKey) FixedLength(spec *common.Spec) uint64 {
return 0
}
func (c *ContractBytecodeKey) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&c.AddressHash,
&c.CodeHash,
)
}
const MaxTrieNodeLength = 1024
type EncodedTrieNode []byte
func (e *EncodedTrieNode) Deserialize(dr *codec.DecodingReader) error {
return dr.ByteList((*[]byte)(e), uint64(MaxTrieNodeLength))
}
func (e EncodedTrieNode) Serialize(w *codec.EncodingWriter) error {
return w.Write(e)
}
func (e EncodedTrieNode) ByteLength() (out uint64) {
return uint64(len(e))
}
func (e *EncodedTrieNode) FixedLength() uint64 {
return 0
}
func (e EncodedTrieNode) HashTreeRoot(hFn tree.HashFn) tree.Root {
return hFn.ByteListHTR(e, MaxTrieNodeLength)
}
func (e *EncodedTrieNode) NodeHash() common.Bytes32 {
return tree.Root(crypto.Keccak256(*e))
}
// A content value type, used when retrieving a trie node.
type TrieNode struct {
Node EncodedTrieNode
}
func (t *TrieNode) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&t.Node,
)
}
func (t TrieNode) Serialize(w *codec.EncodingWriter) error {
return w.Container(&t.Node)
}
func (t TrieNode) ByteLength() (out uint64) {
return codec.ContainerLength(&t.Node)
}
func (t *TrieNode) FixedLength() uint64 {
return 0
}
func (t TrieNode) HashTreeRoot(hFn tree.HashFn) tree.Root {
return hFn.HashTreeRoot(&t.Node)
}
const MaxTrieProofLength = 65
type TrieProof []EncodedTrieNode
func (r *TrieProof) Deserialize(dr *codec.DecodingReader) error {
return dr.List(func() codec.Deserializable {
i := len(*r)
*r = append(*r, EncodedTrieNode{})
return &((*r)[i])
}, 0, MaxTrieProofLength)
}
func (r TrieProof) Serialize(w *codec.EncodingWriter) error {
return w.List(func(i uint64) codec.Serializable {
return &r[i]
}, 0, uint64(len(r)))
}
func (r TrieProof) ByteLength() (out uint64) {
for _, v := range r {
out += v.ByteLength() + codec.OFFSET_SIZE
}
return
}
func (r *TrieProof) FixedLength() uint64 {
return 0
}
func (r TrieProof) HashTreeRoot(hFn tree.HashFn) common.Root {
length := uint64(len(r))
return hFn.ComplexListHTR(func(i uint64) tree.HTR {
if i < length {
return &r[i]
}
return nil
}, length, MaxTrieProofLength)
}
const MaxContractBytecodeLength = 32768
type ContractByteCode []byte
func (t *ContractByteCode) Deserialize(dr *codec.DecodingReader) error {
return dr.ByteList((*[]byte)(t), uint64(MaxContractBytecodeLength))
}
func (t ContractByteCode) Serialize(w *codec.EncodingWriter) error {
return w.Write(t)
}
func (t ContractByteCode) ByteLength() (out uint64) {
return uint64(len(t))
}
func (t *ContractByteCode) FixedLength() uint64 {
return 0
}
func (t ContractByteCode) HashTreeRoot(hFn tree.HashFn) tree.Root {
return hFn.ByteListHTR(t, MaxContractBytecodeLength)
}
// A content value type, used when retrieving contract's bytecode.
type ContractBytecodeContainer struct {
Code ContractByteCode
}
func (t *ContractBytecodeContainer) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(&t.Code)
}
func (t ContractBytecodeContainer) Serialize(w *codec.EncodingWriter) error {
return w.Container(&t.Code)
}
func (t ContractBytecodeContainer) ByteLength() (out uint64) {
return codec.ContainerLength(&t.Code)
}
func (t *ContractBytecodeContainer) FixedLength() uint64 {
return 0
}
func (t ContractBytecodeContainer) HashTreeRoot(hFn tree.HashFn) tree.Root {
return hFn.HashTreeRoot(t.Code)
}
// A content value type, used when offering a trie node from the account trie.
type AccountTrieNodeWithProof struct {
/// An proof for the account trie node.
Proof TrieProof
/// A block at which the proof is anchored.
BlockHash common.Bytes32
}
func (a *AccountTrieNodeWithProof) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&a.Proof,
&a.BlockHash,
)
}
func (a *AccountTrieNodeWithProof) Serialize(w *codec.EncodingWriter) error {
return w.Container(
&a.Proof,
&a.BlockHash,
)
}
func (a *AccountTrieNodeWithProof) ByteLength() uint64 {
return codec.ContainerLength(
&a.Proof,
&a.BlockHash,
)
}
func (a *AccountTrieNodeWithProof) FixedLength() uint64 {
return 0
}
func (a *AccountTrieNodeWithProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&a.Proof,
&a.BlockHash,
)
}
// A content value type, used when offering a trie node from the contract storage trie.
type ContractStorageTrieNodeWithProof struct {
// A proof for the contract storage trie node.
StoregeProof TrieProof
// A proof for the account state.
AccountProof TrieProof
// A block at which the proof is anchored.
BlockHash common.Bytes32
}
func (c *ContractStorageTrieNodeWithProof) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&c.StoregeProof,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractStorageTrieNodeWithProof) Serialize(w *codec.EncodingWriter) error {
return w.Container(
&c.StoregeProof,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractStorageTrieNodeWithProof) ByteLength() uint64 {
return codec.ContainerLength(
&c.StoregeProof,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractStorageTrieNodeWithProof) FixedLength() uint64 {
return 0
}
func (c *ContractStorageTrieNodeWithProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&c.StoregeProof,
&c.AccountProof,
&c.BlockHash,
)
}
// A content value type, used when offering contract's bytecode.
type ContractBytecodeWithProof struct {
// A contract's bytecode.
Code ContractByteCode
// A proof for the account state of the corresponding contract.
AccountProof TrieProof
// A block at which the proof is anchored.
BlockHash common.Bytes32
}
func (c *ContractBytecodeWithProof) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(
&c.Code,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractBytecodeWithProof) Serialize(w *codec.EncodingWriter) error {
return w.Container(
&c.Code,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractBytecodeWithProof) ByteLength() uint64 {
return codec.ContainerLength(
&c.Code,
&c.AccountProof,
&c.BlockHash,
)
}
func (c *ContractBytecodeWithProof) FixedLength() uint64 {
return 0
}
func (c *ContractBytecodeWithProof) HashTreeRoot(hFn tree.HashFn) common.Root {
return hFn.HashTreeRoot(
&c.Code,
&c.AccountProof,
&c.BlockHash,
)
}

File diff suppressed because one or more lines are too long

View file

@ -1,16 +0,0 @@
package storage
import (
"database/sql"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/protolambda/zrnt/eth2/beacon/common"
)
type PortalStorageConfig struct {
StorageCapacityMB uint64
DB *sql.DB
NodeId enode.ID
Spec *common.Spec
NetworkName string
}

View file

@ -1,69 +0,0 @@
package storage
import (
"fmt"
"github.com/holiman/uint256"
)
var ErrContentNotFound = fmt.Errorf("content not found")
var ErrInsufficientRadius = fmt.Errorf("insufficient radius")
var MaxDistance = uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
type ContentType byte
var SizeKey = []byte("size")
type ContentKey struct {
selector ContentType
data []byte
}
func NewContentKey(selector ContentType, data []byte) *ContentKey {
return &ContentKey{
selector: selector,
data: data,
}
}
func (c *ContentKey) Encode() []byte {
res := make([]byte, 0, len(c.data)+1)
res = append(res, byte(c.selector))
res = append(res, c.data...)
return res
}
type ContentStorage interface {
Get(contentKey []byte, contentId []byte) ([]byte, error)
Put(contentKey []byte, contentId []byte, content []byte) error
Radius() *uint256.Int
}
type MockStorage struct {
Db map[string][]byte
}
func NewMockStorage() ContentStorage {
return &MockStorage{
Db: make(map[string][]byte),
}
}
func (m *MockStorage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
if content, ok := m.Db[string(contentId)]; ok {
return content, nil
}
return nil, ErrContentNotFound
}
func (m *MockStorage) Put(contentKey []byte, contentId []byte, content []byte) error {
m.Db[string(contentId)] = content
return nil
}
func (m *MockStorage) Radius() *uint256.Int {
return uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
}

View file

@ -1,300 +0,0 @@
package ethpepple
import (
"bytes"
"encoding/binary"
"runtime"
"sync"
"sync/atomic"
"github.com/cockroachdb/pebble"
"github.com/cockroachdb/pebble/bloom"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
)
const (
// minCache is the minimum amount of memory in megabytes to allocate to pebble
// read and write caching, split half and half.
minCache = 16
// minHandles is the minimum number of files handles to allocate to the open
// database files.
minHandles = 16
// 5% of the content will be deleted when the storage capacity is hit and radius gets adjusted.
contentDeletionFraction = 0.05
)
var _ storage.ContentStorage = &ContentStorage{}
type PeppleStorageConfig struct {
StorageCapacityMB uint64
DB *pebble.DB
NodeId enode.ID
NetworkName string
}
func NewPeppleDB(dataDir string, cache, handles int, namespace string) (*pebble.DB, error) {
// Ensure we have some minimal caching and file guarantees
if cache < minCache {
cache = minCache
}
if handles < minHandles {
handles = minHandles
}
logger := log.New("database", namespace)
logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024), "handles", handles)
// The max memtable size is limited by the uint32 offsets stored in
// internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry.
//
// - MaxUint32 on 64-bit platforms;
// - MaxInt on 32-bit platforms.
//
// It is used when slices are limited to Uint32 on 64-bit platforms (the
// length limit for slices is naturally MaxInt on 32-bit platforms).
//
// Taken from https://github.com/cockroachdb/pebble/blob/master/internal/constants/constants.go
maxMemTableSize := (1<<31)<<(^uint(0)>>63) - 1
// Two memory tables is configured which is identical to leveldb,
// including a frozen memory table and another live one.
memTableLimit := 2
memTableSize := cache * 1024 * 1024 / 2 / memTableLimit
// The memory table size is currently capped at maxMemTableSize-1 due to a
// known bug in the pebble where maxMemTableSize is not recognized as a
// valid size.
//
// TODO use the maxMemTableSize as the maximum table size once the issue
// in pebble is fixed.
if memTableSize >= maxMemTableSize {
memTableSize = maxMemTableSize - 1
}
opt := &pebble.Options{
// Pebble has a single combined cache area and the write
// buffers are taken from this too. Assign all available
// memory allowance for cache.
Cache: pebble.NewCache(int64(cache * 1024 * 1024)),
MaxOpenFiles: handles,
// The size of memory table(as well as the write buffer).
// Note, there may have more than two memory tables in the system.
MemTableSize: uint64(memTableSize),
// MemTableStopWritesThreshold places a hard limit on the size
// of the existent MemTables(including the frozen one).
// Note, this must be the number of tables not the size of all memtables
// according to https://github.com/cockroachdb/pebble/blob/master/options.go#L738-L742
// and to https://github.com/cockroachdb/pebble/blob/master/db.go#L1892-L1903.
MemTableStopWritesThreshold: memTableLimit,
// The default compaction concurrency(1 thread),
// Here use all available CPUs for faster compaction.
MaxConcurrentCompactions: runtime.NumCPU,
// Per-level options. Options for at least one level must be specified. The
// options for the last level are used for all subsequent levels.
Levels: []pebble.LevelOptions{
{TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 4 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 8 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 16 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 32 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 64 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
{TargetFileSize: 128 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)},
},
ReadOnly: false,
}
// Disable seek compaction explicitly. Check https://github.com/ethereum/go-ethereum/pull/20130
// for more details.
opt.Experimental.ReadSamplingMultiplier = -1
db, err := pebble.Open(dataDir+"/"+namespace, opt)
return db, err
}
type ContentStorage struct {
nodeId enode.ID
storageCapacityInBytes uint64
radius atomic.Value
log log.Logger
db *pebble.DB
size atomic.Uint64
writeOptions *pebble.WriteOptions
bytePool sync.Pool
}
func NewPeppleStorage(config PeppleStorageConfig) (storage.ContentStorage, error) {
cs := &ContentStorage{
nodeId: config.NodeId,
db: config.DB,
storageCapacityInBytes: config.StorageCapacityMB * 1000_000,
log: log.New("storage", config.NetworkName),
writeOptions: &pebble.WriteOptions{Sync: false},
bytePool: sync.Pool{
New: func() interface{} {
out := make([]byte, 8)
return &out
},
},
}
cs.radius.Store(storage.MaxDistance)
val, _, err := cs.db.Get(storage.SizeKey)
if err != nil && err != pebble.ErrNotFound {
return nil, err
}
if err == nil {
size := binary.BigEndian.Uint64(val)
// init stage, no need to use lock
cs.size.Store(size)
}
iter, err := cs.db.NewIter(nil)
if err != nil {
return nil, err
}
defer iter.Close()
if iter.Last() && iter.Valid() {
distance := iter.Key()
dis := uint256.NewInt(0)
err = dis.UnmarshalSSZ(distance)
if err != nil {
return nil, err
}
cs.radius.Store(dis)
}
return cs, nil
}
// Get implements storage.ContentStorage.
func (c *ContentStorage) Get(contentKey []byte, contentId []byte) ([]byte, error) {
distance := xor(contentId, c.nodeId[:])
data, closer, err := c.db.Get(distance)
if err != nil && err != pebble.ErrNotFound {
return nil, err
}
if err == pebble.ErrNotFound {
return nil, storage.ErrContentNotFound
}
closer.Close()
return data, nil
}
// Put implements storage.ContentStorage.
func (c *ContentStorage) Put(contentKey []byte, contentId []byte, content []byte) error {
distance := xor(contentId, c.nodeId[:])
valid, err := c.inRadius(distance)
if err != nil {
return err
}
if !valid {
return storage.ErrInsufficientRadius
}
length := uint64(len(contentId)) + uint64(len(content))
newSize := c.size.Add(length)
buf := c.bytePool.Get().(*[]byte)
defer c.bytePool.Put(buf)
binary.BigEndian.PutUint64(*buf, newSize)
batch := c.db.NewBatch()
err = batch.Set(storage.SizeKey, *buf, c.writeOptions)
if err != nil {
return err
}
err = batch.Set(distance, content, c.writeOptions)
if err != nil {
return err
}
err = batch.Commit(c.writeOptions)
if err != nil {
return err
}
if newSize > c.storageCapacityInBytes {
err := c.prune()
if err != nil {
return err
}
}
return nil
}
// Radius implements storage.ContentStorage.
func (c *ContentStorage) Radius() *uint256.Int {
radius := c.radius.Load()
val := radius.(*uint256.Int)
return val
}
func (c *ContentStorage) prune() error {
expectSize := uint64(float64(c.storageCapacityInBytes) * contentDeletionFraction)
var curentSize uint64 = 0
// get the keys to be deleted order by distance desc
iter, err := c.db.NewIter(nil)
if err != nil {
return err
}
batch := c.db.NewBatch()
for iter.Last(); iter.Valid(); iter.Prev() {
if bytes.Equal(iter.Key(), storage.SizeKey) {
continue
}
if curentSize < expectSize {
batch.Delete(iter.Key(), nil)
curentSize += uint64(len(iter.Key())) + uint64(len(iter.Value()))
} else {
distance := iter.Key()
dis := uint256.NewInt(0)
err = dis.UnmarshalSSZ(distance)
if err != nil {
return err
}
c.radius.Store(dis)
break
}
}
newSize := c.size.Add(-curentSize)
buf := c.bytePool.Get().(*[]byte)
defer c.bytePool.Put(buf)
binary.BigEndian.PutUint64(*buf, newSize)
batch.Set(storage.SizeKey, *buf, c.writeOptions)
err = batch.Commit(&pebble.WriteOptions{Sync: true})
if err != nil {
return err
}
return nil
}
func (c *ContentStorage) inRadius(distance []byte) (bool, error) {
dis := uint256.NewInt(0)
err := dis.UnmarshalSSZ(distance)
if err != nil {
return false, err
}
val := c.radius.Load()
radius := val.(*uint256.Int)
return radius.Gt(dis), nil
}
func xor(contentId, nodeId []byte) []byte {
// length of contentId maybe not 32bytes
padding := make([]byte, 32)
if len(contentId) != len(nodeId) {
copy(padding, contentId)
} else {
padding = contentId
}
res := make([]byte, len(padding))
for i := range padding {
res[i] = padding[i] ^ nodeId[i]
}
return res
}

View file

@ -1,163 +0,0 @@
package ethpepple
import (
"testing"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
)
func genBytes(length int) []byte {
res := make([]byte, length)
for i := 0; i < length; i++ {
res[i] = byte(i)
}
return res
}
func TestNewPeppleDB(t *testing.T) {
db, err := NewPeppleDB(t.TempDir(), 16, 16, "test")
assert.NoError(t, err)
defer db.Close()
assert.NotNil(t, db)
}
func setupTestStorage(t *testing.T) storage.ContentStorage {
db, err := NewPeppleDB(t.TempDir(), 16, 16, "test")
assert.NoError(t, err)
t.Cleanup(func() { db.Close() })
config := PeppleStorageConfig{
StorageCapacityMB: 1,
DB: db,
NodeId: uint256.NewInt(0).Bytes32(),
NetworkName: "test",
}
storage, err := NewPeppleStorage(config)
assert.NoError(t, err)
return storage
}
func TestContentStoragePutAndGet(t *testing.T) {
db := setupTestStorage(t)
testCases := []struct {
contentKey []byte
contentId []byte
content []byte
}{
{[]byte("key1"), []byte("id1"), []byte("content1")},
{[]byte("key2"), []byte("id2"), []byte("content2")},
}
for _, tc := range testCases {
err := db.Put(tc.contentKey, tc.contentId, tc.content)
assert.NoError(t, err)
got, err := db.Get(tc.contentKey, tc.contentId)
assert.NoError(t, err)
assert.Equal(t, tc.content, got)
}
}
func TestRadius(t *testing.T) {
db := setupTestStorage(t)
radius := db.Radius()
assert.NotNil(t, radius)
assert.True(t, radius.Eq(storage.MaxDistance))
}
func TestXOR(t *testing.T) {
testCases := []struct {
contentId []byte
nodeId []byte
expected []byte
}{
{
contentId: []byte{0x01},
nodeId: make([]byte, 32),
expected: append([]byte{0x01}, make([]byte, 31)...),
},
{
contentId: []byte{0xFF},
nodeId: []byte{0x0F},
expected: []byte{0xF0},
},
}
for _, tc := range testCases {
result := xor(tc.contentId, tc.nodeId)
assert.Equal(t, tc.expected, result)
}
}
// the capacity is 1MB, so prune will delete over 50Kb content
func TestPrune(t *testing.T) {
db := setupTestStorage(t)
// the nodeId is zeros, so contentKey and contentId is the same
testcases := []struct {
contentKey [32]byte
content []byte
outOfRadius bool
err error
}{
{
contentKey: uint256.NewInt(1).Bytes32(),
content: genBytes(900_000),
},
{
contentKey: uint256.NewInt(2).Bytes32(),
content: genBytes(40_000),
},
{
contentKey: uint256.NewInt(3).Bytes32(),
content: genBytes(20_000),
err: storage.ErrContentNotFound,
},
{
contentKey: uint256.NewInt(4).Bytes32(),
content: genBytes(20_000),
err: storage.ErrContentNotFound,
},
{
contentKey: uint256.NewInt(5).Bytes32(),
content: genBytes(20_000),
err: storage.ErrContentNotFound,
},
{
contentKey: uint256.NewInt(6).Bytes32(),
content: genBytes(20_000),
err: storage.ErrInsufficientRadius,
outOfRadius: true,
},
{
contentKey: uint256.NewInt(7).Bytes32(),
content: genBytes(20_000),
err: storage.ErrInsufficientRadius,
outOfRadius: true,
},
}
for _, val := range testcases {
err := db.Put(val.contentKey[:], val.contentKey[:], val.content)
if err != nil {
assert.Equal(t, val.err, err)
}
}
for _, val := range testcases {
content, err := db.Get(val.contentKey[:], val.contentKey[:])
if err == nil {
assert.Equal(t, val.content, content)
} else if !val.outOfRadius {
assert.Equal(t, val.err, err)
}
}
radius := db.Radius()
data, err := radius.MarshalSSZ()
assert.NoError(t, err)
actual := uint256.NewInt(2).Bytes32()
assert.Equal(t, data, actual[:])
}

View file

@ -1,13 +0,0 @@
package web3
import "runtime"
type API struct{}
func (p *API) ClientVersion() string {
// TODO add version
name := "Shisui"
name += "/" + runtime.GOOS + "-" + runtime.GOARCH
name += "/" + runtime.Version()
return name
}