mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
remove shisui
Signed-off-by: Chen Kai <281165273grape@gmail.com>
This commit is contained in:
parent
b209acdc7d
commit
58195822c4
86 changed files with 0 additions and 152460 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 := ðapi.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -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']
|
||||
Binary file not shown.
|
|
@ -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
|
||||
Binary file not shown.
|
|
@ -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
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -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"}
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"6718463": {
|
||||
"content_key": "0x12a083660000000000",
|
||||
"content_value": "0xbba4da9670010000b30400001d34030000000000000000000000000000000000000000000000000000000000a4f8b2415bbca66d73597343afead504bd8282523df27153543b2de8973b7474ace652bb73991c3b63f7185a17333c8aea619a69b69863a5d79810dad6c363d3df460765de20d3c8f56bcd9490f15e2d9e122b3f6173cea76f6d0d16074ce0f0ecde5df1b81f36c08e91ce803c25add312bfd8cb759a81b7cfd158d724959cf22e83908e043f77bdc5992806617ef1fa0a4f1985d9aa1b67371d3580be8c2c64ffffffffffffffffffffffffffffbfffff7fffffffffffffffffffffffffffffffffffffffffffffffffffdefffffffffffffffffffeffffffffffffffffefffa480f81d481c5aa8439f8c65f86c69d3b1570cfd95b61300f7e29c5b0954c0c0fb080bba0e8ba60dead40c45355332ed06982fcaf53ad1ad4a7e6e39091ee7e5602d4a30b6d46e12047c31451f246bf43c4a3fbdb1d1de2f2180a776ef71fc3d0084660000000000ff83660000000000e5f4020000000000007b8211614b9c5331a1de2691ad7cdde1ca113b4e51b9f2757a4b502833f9234a06eb807d82b9175dd085748ade76aaa86fb4eca48bc8deb01b9c56a267a896d98469820ef7d34f8610e134c6eaf8d379e83cf6c3ab0663fba828edab344dc5f40000008e2a532cfdfdce86b27b26f611f1df0e2b00a54d18c6980e69ad28a4b70bf480336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71b4c4e01065cd22a0d2f27a544ff8315b9e28f6270a63f543831778f32648ac3a15ecbea44f91b9b4a28ec8308a30a4b6252f554a2088bb1c73ea01013ba4963dfeebabe6b0418ec13b30aadf129f5dcdd4f70ceaa56499df0b3ba9d8fe6ca410132a29cf387371294137003adb139ce9f06b6f7dbf32ee01da91016a485a8aab5669168efc3d3a200b2ee0f25d937a413dfc38e44e211f02432a01a83088e700808c302002139aad8496010300cd220b14288429d8a6049945000a3b60f00f00109019341a0129a08906314848650817002cb98894a328084521a87e6d0a64e8080004ac0805000800e29808294016508a2004001304002a02320c4705501ac008a62848701040012a08e5c8b69010f1009f80408894857053e1b940e2c9004912860e26112080610d002098242000ca001420209a42c141d0c86087509344ced8a32e0204410a0196025a12106104e20008ad4449d5043208090008810201820209108607a0fa1004aa323410050222867024e0d172a82821b02c00020513152006484c800b10f0420802c0244d0850288c6541dc34cabc32f5d355aef4017425cab95c1de0474d483623e23c9bf13d9a18f1597e930b010000000080c3c901000000000ca88800000000004b6094640000000038020000ab9bf66d04000000000000000000000000000000000000000000000000000000797be971185ac699fa7288c87457c3611b443fb98483fddf282dfa7c5b4b27058eddbcd8b5de9b747c57c827ceba820d19e987ff6740eed38ccbc947696d71e3e2c3b96c9200d7d5c66fb4e8458067d6420e64647682ca2f0798ae817d2bc19468747470733a2f2f6574682d6275696c6465722e636f6da08366000000000037b10700000000002a7315c8ddfc25dc2266a6b221cb8f9fdf641970ab1f65a2754df4e14c432b9c446c604131913bed45976c4a8ea27df843a72d622f80ef15da622f0d56ec1ffe3021190177b0405c4fa1a0311a493c4dd095f24d386be6a03f5838a6b1008665f4000000f5c98fc152bf40e1ce216c8839b8ddd42ea5b355f0b5c700fc9b2cb7c802e1c8336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71a533e05d648cb9635382f38b778dc3c76b18e4eab2d017fdb24c1c0e32d2991707a45ce3f26e443dafa697a76bf24122c9b19b57eebe798668dbd1a0da7c900795222290dd7278aa3ddd389cc1e1d165cc4bafe5c658153cdef5850f97c4d4fb6706b82310414a00fe7dc34b3043e0c7a8a24e810e3cec9de3e1dd7ce06bc191efd2273a357da4436fc4bf82069f6cb87d3d605aeda1336ac513f358b4296b8da51232896ad271063d93404eaa6b80960fa2d4b41d74a544eef94731f0b83ba2701e33b49e3191098c3369bc4658d82f536cb9d38285c51c7844292ced06dd2e8148f0bcd585dc1104e64aa930045e83d828ab436a0e587637e5ef83044d1c2918da284d7072847132885d443640979907485847368e32501345b0dc24c904c06732cc17d944b26181b0c05c6461bccbe4f7b2180a282b6871aa6472fa36cdc49ec50cf69d3eee4b26fe02bf48f39efb549d3963d19e4112c7250c580543516204ff1b0675680cfea02ac09f0b23d6e28c14b08421b3a10a9788442457d732b120c5c466c54ad938dedd3cd3701d9b32fa15dc8db3d6b1ea5d8495732477a72b34efcaf8c349aa40d8e1f21f226ca11182cff00921930b010000000080c3c901000000009104e90000000000d75b9464000000003802000076ab1c8604000000000000000000000000000000000000000000000000000000844f7a7e7585362114fc88a70654146215ab4eaad65ad54b64975840b78d365e8b75e78616a92294e747589c3ee4a845c2962e1cf6cd7fe8bfbd09439e5e6d5e4d4efdf181473803e734d97891388e1b50628658ac818599cf2cb069c8e6b38b6265617665726275696c642e6f7267"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"6718463": {
|
||||
"content_key": "0x130084660000000000",
|
||||
"content_value": "0xbba4da96ac000000ffffffffffffffffffffffffffffbfffff7fffffffffffffffffffffffffffffffffffffffffffffffffffdefffffffffffffffffffeffffffffffffffffefffa480f81d481c5aa8439f8c65f86c69d3b1570cfd95b61300f7e29c5b0954c0c0fb080bba0e8ba60dead40c45355332ed06982fcaf53ad1ad4a7e6e39091ee7e5602d4a30b6d46e12047c31451f246bf43c4a3fbdb1d1de2f2180a776ef71fc3d0084660000000000ff83660000000000e5f4020000000000007b8211614b9c5331a1de2691ad7cdde1ca113b4e51b9f2757a4b502833f9234a06eb807d82b9175dd085748ade76aaa86fb4eca48bc8deb01b9c56a267a896d98469820ef7d34f8610e134c6eaf8d379e83cf6c3ab0663fba828edab344dc5f40000008e2a532cfdfdce86b27b26f611f1df0e2b00a54d18c6980e69ad28a4b70bf480336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0edb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71b4c4e01065cd22a0d2f27a544ff8315b9e28f6270a63f543831778f32648ac3a15ecbea44f91b9b4a28ec8308a30a4b6252f554a2088bb1c73ea01013ba4963dfeebabe6b0418ec13b30aadf129f5dcdd4f70ceaa56499df0b3ba9d8fe6ca410132a29cf387371294137003adb139ce9f06b6f7dbf32ee01da91016a485a8aab5669168efc3d3a200b2ee0f25d937a413dfc38e44e211f02432a01a83088e700808c302002139aad8496010300cd220b14288429d8a6049945000a3b60f00f00109019341a0129a08906314848650817002cb98894a328084521a87e6d0a64e8080004ac0805000800e29808294016508a2004001304002a02320c4705501ac008a62848701040012a08e5c8b69010f1009f80408894857053e1b940e2c9004912860e26112080610d002098242000ca001420209a42c141d0c86087509344ced8a32e0204410a0196025a12106104e20008ad4449d5043208090008810201820209108607a0fa1004aa323410050222867024e0d172a82821b02c00020513152006484c800b10f0420802c0244d0850288c6541dc34cabc32f5d355aef4017425cab95c1de0474d483623e23c9bf13d9a18f1597e930b010000000080c3c901000000000ca88800000000004b6094640000000038020000ab9bf66d04000000000000000000000000000000000000000000000000000000797be971185ac699fa7288c87457c3611b443fb98483fddf282dfa7c5b4b27058eddbcd8b5de9b747c57c827ceba820d19e987ff6740eed38ccbc947696d71e3e2c3b96c9200d7d5c66fb4e8458067d6420e64647682ca2f0798ae817d2bc19468747470733a2f2f6574682d6275696c6465722e636f6d"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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)))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
//}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
1
portalnetwork/history/testdata/epoch.txt
vendored
1
portalnetwork/history/testdata/epoch.txt
vendored
File diff suppressed because one or more lines are too long
18
portalnetwork/history/testdata/header_rlps.json
vendored
18
portalnetwork/history/testdata/header_rlps.json
vendored
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
42
portalnetwork/history/testdata/hive_gossip.yaml
vendored
42
portalnetwork/history/testdata/hive_gossip.yaml
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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("")
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
37
portalnetwork/state/testdata/data.json
vendored
37
portalnetwork/state/testdata/data.json
vendored
File diff suppressed because one or more lines are too long
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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[:])
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue