mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +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