Compare commits

...

6 Commits

Author SHA1 Message Date
Pepper Lebeck-Jobe
a289071ddd Set linkmode="c-archive" just to get the test to compile
This obvioulsly won't run because it craetes a c library archive and
not a go test binary. But, it shuts up blaze.

I suppose an alternative would have been just to delete the go_test
rule outright.
2024-10-14 16:01:23 +02:00
Potuz
24a6025426 Use hashtree 2024-10-10 15:29:06 -03:00
Sammy Rosso
57cc4950c0 Add /eth/v2/beacon/blocks/{block_id}/attestations (#14478)
* add endpoint

* changelog

* fix response

* improvement

* fix test

* James' review

* fix test

* fix version check

* add test for non electra V2

* Review items

* James' review

* Radek' review

* Update CHANGELOG.md

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2024-10-10 10:19:26 +00:00
Nishant Das
2c981d5564 Reboot Discovery Listener (#14487)
* Add Current Changes To Routine

* Add In New Test

* Add Feature Flag

* Add Discovery Rebooter feature

* Do Not Export Mutex And Use Zero Value Mutex

* Wrap Error For Better Debugging

* Fix Function Name and Add Specific Test For it

* Manu's Review
2024-10-10 08:22:42 +00:00
zhaochonghe
492c8af83f Fix mesh size in libp2p-pubsub (#14521)
* Fix Mesh size in libp2p-pubsub

* Update pubsub.go

* Update CHANGELOG.md
2024-10-09 14:08:26 +00:00
Rupam Dey
e40d2cbd2c feat: add Bellatrix tests for light clients (#14520)
* add tests for Bellatrix

* update `CHANGELOG.md`

* Update CHANGELOG.md

Co-authored-by: Radosław Kapka <radoslaw.kapka@gmail.com>

---------

Co-authored-by: Radosław Kapka <radoslaw.kapka@gmail.com>
2024-10-09 09:02:48 +00:00
25 changed files with 933 additions and 198 deletions

View File

@@ -16,13 +16,16 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Light client support: Implement `BlockToLightClientHeader` function.
- Light client support: Consensus types.
- GetBeaconStateV2: add Electra case.
- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875)
- Tests to ensure sepolia config matches the official upstream yaml
- HTTP endpoint for PublishBlobs
- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875).
- Tests to ensure sepolia config matches the official upstream yaml.
- HTTP endpoint for PublishBlobs.
- GetBlockV2, GetBlindedBlock, ProduceBlockV2, ProduceBlockV3: add Electra case.
- Add Electra support and tests for light client functions
- Add Electra support and tests for light client functions.
- fastssz version bump (better error messages).
- SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413)
- Add Bellatrix tests for light client functions.
- Add Discovery Rebooter Feature.
- Added GetBlockAttestationsV2 endpoint.
### Changed
@@ -80,6 +83,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Light client support: fix light client attested header execution fields' wrong version bug.
- Testing: added custom matcher for better push settings testing.
- Registered `GetDepositSnapshot` Beacon API endpoint.
- Fixed mesh size by appending `gParams.Dhi = gossipSubDhi`
### Security

View File

@@ -133,6 +133,13 @@ type GetBlockAttestationsResponse struct {
Data []*Attestation `json:"data"`
}
type GetBlockAttestationsV2Response struct {
Version string `json:"version"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data json.RawMessage `json:"data"` // Accepts both `Attestation` and `AttestationElectra` types
}
type GetStateRootResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`

View File

@@ -364,6 +364,26 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
})
t.Run("Bellatrix", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix()
container, err := lightClient.BlockToLightClientHeader(l.Block)
require.NoError(t, err)
header := container.GetHeaderAltair()
require.NotNil(t, header, "header is nil")
parentRoot := l.Block.Block().ParentRoot()
stateRoot := l.Block.Block().StateRoot()
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
require.NoError(t, err)
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
})
t.Run("Capella", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)

View File

@@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/p2p/discover"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/host"
"github.com/prysmaticlabs/go-bitfield"
@@ -236,7 +235,7 @@ func TestService_BroadcastAttestationWithDiscoveryAttempts(t *testing.T) {
bootNode := bootListener.Self()
subnet := uint64(5)
var listeners []*discover.UDPv5
var listeners []*listenerWrapper
var hosts []host.Host
// setup other nodes.
cfg = &Config{

View File

@@ -50,7 +50,7 @@ func TestPeer_AtMaxLimit(t *testing.T) {
}()
for i := 0; i < highWatermarkBuffer; i++ {
addPeer(t, s.peers, peers.PeerConnected)
addPeer(t, s.peers, peers.PeerConnected, false)
}
// create alternate host
@@ -159,7 +159,7 @@ func TestService_RejectInboundPeersBeyondLimit(t *testing.T) {
inboundLimit += 1
// Add in up to inbound peer limit.
for i := 0; i < int(inboundLimit); i++ {
addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
}
valid = s.InterceptAccept(&maEndpoints{raddr: multiAddress})
if valid {

View File

@@ -24,6 +24,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
type ListenerRebooter interface {
Listener
RebootListener() error
}
// Listener defines the discovery V5 network interface that is used
// to communicate with other peers.
type Listener interface {
@@ -47,6 +52,87 @@ type quicProtocol uint16
// quicProtocol is the "quic" key, which holds the QUIC port of the node.
func (quicProtocol) ENRKey() string { return "quic" }
type listenerWrapper struct {
mu sync.RWMutex
listener *discover.UDPv5
listenerCreator func() (*discover.UDPv5, error)
}
func newListener(listenerCreator func() (*discover.UDPv5, error)) (*listenerWrapper, error) {
rawListener, err := listenerCreator()
if err != nil {
return nil, errors.Wrap(err, "could not create new listener")
}
return &listenerWrapper{
listener: rawListener,
listenerCreator: listenerCreator,
}, nil
}
func (l *listenerWrapper) Self() *enode.Node {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.Self()
}
func (l *listenerWrapper) Close() {
l.mu.RLock()
defer l.mu.RUnlock()
l.listener.Close()
}
func (l *listenerWrapper) Lookup(id enode.ID) []*enode.Node {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.Lookup(id)
}
func (l *listenerWrapper) Resolve(node *enode.Node) *enode.Node {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.Resolve(node)
}
func (l *listenerWrapper) RandomNodes() enode.Iterator {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.RandomNodes()
}
func (l *listenerWrapper) Ping(node *enode.Node) error {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.Ping(node)
}
func (l *listenerWrapper) RequestENR(node *enode.Node) (*enode.Node, error) {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.RequestENR(node)
}
func (l *listenerWrapper) LocalNode() *enode.LocalNode {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.LocalNode()
}
func (l *listenerWrapper) RebootListener() error {
l.mu.Lock()
defer l.mu.Unlock()
// Close current listener
l.listener.Close()
newListener, err := l.listenerCreator()
if err != nil {
return err
}
l.listener = newListener
return nil
}
// RefreshENR uses an epoch to refresh the enr entry for our node
// with the tracked committee ids for the epoch, allowing our node
// to be dynamically discoverable by others given our tracked committee ids.
@@ -110,55 +196,78 @@ func (s *Service) RefreshENR() {
func (s *Service) listenForNewNodes() {
iterator := filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer)
defer iterator.Close()
connectivityTicker := time.NewTicker(1 * time.Minute)
thresholdCount := 0
for {
// Exit if service's context is canceled.
if s.ctx.Err() != nil {
break
}
if s.isPeerAtLimit(false /* inbound */) {
// Pause the main loop for a period to stop looking
// for new peers.
log.Trace("Not looking for peers, at peer limit")
time.Sleep(pollingPeriod)
continue
}
wantedCount := s.wantedPeerDials()
if wantedCount == 0 {
log.Trace("Not looking for peers, at peer limit")
time.Sleep(pollingPeriod)
continue
}
// Restrict dials if limit is applied.
if flags.MaxDialIsActive() {
wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials)
}
wantedNodes := enode.ReadNodes(iterator, wantedCount)
wg := new(sync.WaitGroup)
for i := 0; i < len(wantedNodes); i++ {
node := wantedNodes[i]
peerInfo, _, err := convertToAddrInfo(node)
if err != nil {
log.WithError(err).Error("Could not convert to peer info")
select {
case <-s.ctx.Done():
return
case <-connectivityTicker.C:
// Skip the connectivity check if not enabled.
if !features.Get().EnableDiscoveryReboot {
continue
}
if peerInfo == nil {
if !s.isBelowOutboundPeerThreshold() {
// Reset counter if we are beyond the threshold
thresholdCount = 0
continue
}
// Make sure that peer is not dialed too often, for each connection attempt there's a backoff period.
s.Peers().RandomizeBackOff(peerInfo.ID)
wg.Add(1)
go func(info *peer.AddrInfo) {
if err := s.connectWithPeer(s.ctx, *info); err != nil {
log.WithError(err).Tracef("Could not connect with peer %s", info.String())
thresholdCount++
// Reboot listener if connectivity drops
if thresholdCount > 5 {
log.WithField("outboundConnectionCount", len(s.peers.OutboundConnected())).Warn("Rebooting discovery listener, reached threshold.")
if err := s.dv5Listener.RebootListener(); err != nil {
log.WithError(err).Error("Could not reboot listener")
continue
}
wg.Done()
}(peerInfo)
iterator = filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer)
thresholdCount = 0
}
default:
if s.isPeerAtLimit(false /* inbound */) {
// Pause the main loop for a period to stop looking
// for new peers.
log.Trace("Not looking for peers, at peer limit")
time.Sleep(pollingPeriod)
continue
}
wantedCount := s.wantedPeerDials()
if wantedCount == 0 {
log.Trace("Not looking for peers, at peer limit")
time.Sleep(pollingPeriod)
continue
}
// Restrict dials if limit is applied.
if flags.MaxDialIsActive() {
wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials)
}
wantedNodes := enode.ReadNodes(iterator, wantedCount)
wg := new(sync.WaitGroup)
for i := 0; i < len(wantedNodes); i++ {
node := wantedNodes[i]
peerInfo, _, err := convertToAddrInfo(node)
if err != nil {
log.WithError(err).Error("Could not convert to peer info")
continue
}
if peerInfo == nil {
continue
}
// Make sure that peer is not dialed too often, for each connection attempt there's a backoff period.
s.Peers().RandomizeBackOff(peerInfo.ID)
wg.Add(1)
go func(info *peer.AddrInfo) {
if err := s.connectWithPeer(s.ctx, *info); err != nil {
log.WithError(err).Tracef("Could not connect with peer %s", info.String())
}
wg.Done()
}(peerInfo)
}
wg.Wait()
}
wg.Wait()
}
}
@@ -299,14 +408,17 @@ func (s *Service) createLocalNode(
func (s *Service) startDiscoveryV5(
addr net.IP,
privKey *ecdsa.PrivateKey,
) (*discover.UDPv5, error) {
listener, err := s.createListener(addr, privKey)
) (*listenerWrapper, error) {
createListener := func() (*discover.UDPv5, error) {
return s.createListener(addr, privKey)
}
wrappedListener, err := newListener(createListener)
if err != nil {
return nil, errors.Wrap(err, "could not create listener")
}
record := listener.Self()
record := wrappedListener.Self()
log.WithField("ENR", record.String()).Info("Started discovery v5")
return listener, nil
return wrappedListener, nil
}
// filterPeer validates each node that we retrieve from our dht. We
@@ -398,6 +510,22 @@ func (s *Service) isPeerAtLimit(inbound bool) bool {
return activePeers >= maxPeers || numOfConns >= maxPeers
}
// isBelowOutboundPeerThreshold checks if the number of outbound peers that
// we are connected to satisfies the minimum expected outbound peer count
// according to our peer limit.
func (s *Service) isBelowOutboundPeerThreshold() bool {
maxPeers := int(s.cfg.MaxPeers)
inBoundLimit := s.Peers().InboundLimit()
// Impossible Condition
if maxPeers < inBoundLimit {
return false
}
outboundFloor := maxPeers - inBoundLimit
outBoundThreshold := outboundFloor / 2
outBoundCount := len(s.Peers().OutboundConnected())
return outBoundCount < outBoundThreshold
}
func (s *Service) wantedPeerDials() int {
maxPeers := int(s.cfg.MaxPeers)

View File

@@ -95,7 +95,7 @@ func TestStartDiscV5_DiscoverAllPeers(t *testing.T) {
bootNode := bootListener.Self()
var listeners []*discover.UDPv5
var listeners []*listenerWrapper
for i := 1; i <= 5; i++ {
port = 3000 + i
cfg := &Config{
@@ -231,6 +231,37 @@ func TestCreateLocalNode(t *testing.T) {
}
}
func TestRebootDiscoveryListener(t *testing.T) {
port := 1024
ipAddr, pkey := createAddrAndPrivKey(t)
s := &Service{
genesisTime: time.Now(),
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
require.NoError(t, err)
currentPubkey := listener.Self().Pubkey()
currentID := listener.Self().ID()
currentPort := listener.Self().UDP()
currentAddr := listener.Self().IP()
assert.NoError(t, listener.RebootListener())
newPubkey := listener.Self().Pubkey()
newID := listener.Self().ID()
newPort := listener.Self().UDP()
newAddr := listener.Self().IP()
assert.Equal(t, true, currentPubkey.Equal(newPubkey))
assert.Equal(t, currentID, newID)
assert.Equal(t, currentPort, newPort)
assert.Equal(t, currentAddr.String(), newAddr.String())
}
func TestMultiAddrsConversion_InvalidIPAddr(t *testing.T) {
addr := net.ParseIP("invalidIP")
_, pkey := createAddrAndPrivKey(t)
@@ -347,19 +378,44 @@ func TestInboundPeerLimit(t *testing.T) {
}
for i := 0; i < 30; i++ {
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
}
require.Equal(t, true, s.isPeerAtLimit(false), "not at limit for outbound peers")
require.Equal(t, false, s.isPeerAtLimit(true), "at limit for inbound peers")
for i := 0; i < highWatermarkBuffer; i++ {
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
}
require.Equal(t, true, s.isPeerAtLimit(true), "not at limit for inbound peers")
}
func TestOutboundPeerThreshold(t *testing.T) {
fakePeer := testp2p.NewTestP2P(t)
s := &Service{
cfg: &Config{MaxPeers: 30},
ipLimiter: leakybucket.NewCollector(ipLimit, ipBurst, 1*time.Second, false),
peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
PeerLimit: 30,
ScorerParams: &scorers.Config{},
}),
host: fakePeer.BHost,
}
for i := 0; i < 2; i++ {
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true)
}
require.Equal(t, true, s.isBelowOutboundPeerThreshold(), "not at outbound peer threshold")
for i := 0; i < 3; i++ {
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true)
}
require.Equal(t, false, s.isBelowOutboundPeerThreshold(), "still at outbound peer threshold")
}
func TestUDPMultiAddress(t *testing.T) {
port := 6500
ipAddr, pkey := createAddrAndPrivKey(t)
@@ -370,7 +426,11 @@ func TestUDPMultiAddress(t *testing.T) {
genesisTime: genesisTime,
genesisValidatorsRoot: genesisValidatorsRoot,
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
require.NoError(t, err)
defer listener.Close()
s.dv5Listener = listener
@@ -417,7 +477,7 @@ func TestCorrectUDPVersion(t *testing.T) {
}
// addPeer is a helper to add a peer with a given connection state)
func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState) peer.ID {
func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState, outbound bool) peer.ID {
// Set up some peers with different states
mhBytes := []byte{0x11, 0x04}
idBytes := make([]byte, 4)
@@ -426,7 +486,11 @@ func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState)
mhBytes = append(mhBytes, idBytes...)
id, err := peer.IDFromBytes(mhBytes)
require.NoError(t, err)
p.Add(new(enr.Record), id, nil, network.DirInbound)
dir := network.DirInbound
if outbound {
dir = network.DirOutbound
}
p.Add(new(enr.Record), id, nil, dir)
p.SetConnectionState(id, state)
p.SetMetadata(id, wrapper.WrappedMetadataV0(&ethpb.MetaDataV0{
SeqNumber: 0,
@@ -455,7 +519,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
assert.NoError(t, err)
s.dv5Listener = listener
s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0))
@@ -484,7 +551,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
assert.NoError(t, err)
s.dv5Listener = listener
s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0))
@@ -506,7 +576,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
assert.NoError(t, err)
// Update params
@@ -537,7 +610,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
assert.NoError(t, err)
// Update params
@@ -575,7 +651,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
cfg: &Config{UDPPort: uint(port)},
}
listener, err := s.createListener(ipAddr, pkey)
createListener := func() (*discover.UDPv5, error) {
return s.createListener(ipAddr, pkey)
}
listener, err := newListener(createListener)
assert.NoError(t, err)
// Update params

View File

@@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
ma "github.com/multiformats/go-multiaddr"
@@ -52,7 +51,7 @@ func TestStartDiscv5_DifferentForkDigests(t *testing.T) {
StateNotifier: &mock.MockStateNotifier{},
}
var listeners []*discover.UDPv5
var listeners []*listenerWrapper
for i := 1; i <= 5; i++ {
port := 3000 + i
cfg.UDPPort = uint(port)
@@ -139,7 +138,7 @@ func TestStartDiscv5_SameForkDigests_DifferentNextForkData(t *testing.T) {
UDPPort: uint(port),
}
var listeners []*discover.UDPv5
var listeners []*listenerWrapper
for i := 1; i <= 5; i++ {
port := 3000 + i
cfg.UDPPort = uint(port)

View File

@@ -182,6 +182,7 @@ func pubsubGossipParam() pubsub.GossipSubParams {
gParams := pubsub.DefaultGossipSubParams()
gParams.Dlo = gossipSubDlo
gParams.D = gossipSubD
gParams.Dhi = gossipSubDhi
gParams.HeartbeatInterval = gossipSubHeartbeatInterval
gParams.HistoryLength = gossipSubMcacheLen
gParams.HistoryGossip = gossipSubMcacheGossip

View File

@@ -71,7 +71,7 @@ type Service struct {
subnetsLock map[uint64]*sync.RWMutex
subnetsLockLock sync.Mutex // Lock access to subnetsLock
initializationLock sync.Mutex
dv5Listener Listener
dv5Listener ListenerRebooter
startupErr error
ctx context.Context
host host.Host

View File

@@ -8,7 +8,6 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
@@ -69,6 +68,8 @@ func (mockListener) RandomNodes() enode.Iterator {
panic("implement me")
}
func (mockListener) RebootListener() error { panic("implement me") }
func createHost(t *testing.T, port int) (host.Host, *ecdsa.PrivateKey, net.IP) {
_, pkey := createAddrAndPrivKey(t)
ipAddr := net.ParseIP("127.0.0.1")
@@ -210,7 +211,7 @@ func TestListenForNewNodes(t *testing.T) {
bootNode := bootListener.Self()
var listeners []*discover.UDPv5
var listeners []*listenerWrapper
var hosts []host.Host
// setup other nodes.
cs := startup.NewClockSynchronizer()

View File

@@ -585,6 +585,15 @@ func (s *Service) beaconEndpoints(
handler: server.GetBlockAttestations,
methods: []string{http.MethodGet},
},
{
template: "/eth/v2/beacon/blocks/{block_id}/attestations",
name: namespace + ".GetBlockAttestationsV2",
middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.GetBlockAttestations,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/blinded_blocks/{block_id}",
name: namespace + ".GetBlindedBlock",

View File

@@ -36,6 +36,7 @@ func Test_endpoints(t *testing.T) {
"/eth/v2/beacon/blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet},
"/eth/v2/beacon/blocks/{block_id}/attestations": {http.MethodGet},
"/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet},
"/eth/v1/beacon/deposit_snapshot": {http.MethodGet},
"/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet},

View File

@@ -200,16 +200,10 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestations")
defer span.End()
blockId := r.PathValue("block_id")
if blockId == "" {
httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest)
blk, isOptimistic, root := s.blockData(ctx, w, r)
if blk == nil {
return
}
blk, err := s.Blocker.Block(ctx, []byte(blockId))
if !shared.WriteBlockFetchError(w, blk, err) {
return
}
consensusAtts := blk.Block().Body().Attestations()
atts := make([]*structs.Attestation, len(consensusAtts))
for i, att := range consensusAtts {
@@ -221,17 +215,6 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
return
}
}
root, err := blk.Block().HashTreeRoot()
if err != nil {
httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError)
return
}
isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root)
if err != nil {
httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError)
return
}
resp := &structs.GetBlockAttestationsResponse{
Data: atts,
ExecutionOptimistic: isOptimistic,
@@ -240,6 +223,79 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
httputil.WriteJson(w, resp)
}
// GetBlockAttestationsV2 retrieves attestation included in requested block.
func (s *Server) GetBlockAttestationsV2(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestationsV2")
defer span.End()
blk, isOptimistic, root := s.blockData(ctx, w, r)
if blk == nil {
return
}
consensusAtts := blk.Block().Body().Attestations()
v := blk.Block().Version()
var attStructs []interface{}
if v >= version.Electra {
for _, att := range consensusAtts {
a, ok := att.(*eth.AttestationElectra)
if !ok {
httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestations electra of type %T", att), http.StatusInternalServerError)
return
}
attStruct := structs.AttElectraFromConsensus(a)
attStructs = append(attStructs, attStruct)
}
} else {
for _, att := range consensusAtts {
a, ok := att.(*eth.Attestation)
if !ok {
httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestation of type %T", att), http.StatusInternalServerError)
return
}
attStruct := structs.AttFromConsensus(a)
attStructs = append(attStructs, attStruct)
}
}
attBytes, err := json.Marshal(attStructs)
if err != nil {
httputil.HandleError(w, fmt.Sprintf("failed to marshal attestations: %v", err), http.StatusInternalServerError)
return
}
resp := &structs.GetBlockAttestationsV2Response{
Version: version.String(v),
ExecutionOptimistic: isOptimistic,
Finalized: s.FinalizationFetcher.IsFinalized(ctx, root),
Data: attBytes,
}
httputil.WriteJson(w, resp)
}
func (s *Server) blockData(ctx context.Context, w http.ResponseWriter, r *http.Request) (interfaces.ReadOnlySignedBeaconBlock, bool, [32]byte) {
blockId := r.PathValue("block_id")
if blockId == "" {
httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest)
return nil, false, [32]byte{}
}
blk, err := s.Blocker.Block(ctx, []byte(blockId))
if !shared.WriteBlockFetchError(w, blk, err) {
return nil, false, [32]byte{}
}
root, err := blk.Block().HashTreeRoot()
if err != nil {
httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError)
return nil, false, [32]byte{}
}
isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root)
if err != nil {
httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError)
return nil, false, [32]byte{}
}
return blk, isOptimistic, root
}
// PublishBlindedBlock instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct
// and publish a SignedBeaconBlock by swapping out the transactions_root for the corresponding full list of `transactions`.
// The beacon node should broadcast a newly constructed SignedBeaconBlock to the beacon network, to be included in the

View File

@@ -519,137 +519,141 @@ func TestGetBlockSSZV2(t *testing.T) {
}
func TestGetBlockAttestations(t *testing.T) {
t.Run("ok", func(t *testing.T) {
b := util.NewBeaconBlock()
b.Block.Body.Attestations = []*eth.Attestation{
{
AggregationBits: bitfield.Bitlist{0x00},
Data: &eth.AttestationData{
Slot: 123,
CommitteeIndex: 123,
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
Source: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
Target: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
preElectraAtts := []*eth.Attestation{
{
AggregationBits: bitfield.Bitlist{0x00},
Data: &eth.AttestationData{
Slot: 123,
CommitteeIndex: 123,
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
Source: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
Signature: bytesutil.PadTo([]byte("sig1"), 96),
},
{
AggregationBits: bitfield.Bitlist{0x01},
Data: &eth.AttestationData{
Slot: 456,
CommitteeIndex: 456,
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
Source: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
Target: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
Target: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
Signature: bytesutil.PadTo([]byte("sig2"), 96),
},
}
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
mockChainService := &chainMock.ChainService{
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
Signature: bytesutil.PadTo([]byte("sig1"), 96),
},
{
AggregationBits: bitfield.Bitlist{0x01},
Data: &eth.AttestationData{
Slot: 456,
CommitteeIndex: 456,
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
Source: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
Target: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
},
Signature: bytesutil.PadTo([]byte("sig2"), 96),
},
}
electraAtts := []*eth.AttestationElectra{
{
AggregationBits: bitfield.Bitlist{0x00},
Data: &eth.AttestationData{
Slot: 123,
CommitteeIndex: 123,
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
Source: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
Target: &eth.Checkpoint{
Epoch: 123,
Root: bytesutil.PadTo([]byte("root1"), 32),
},
},
Signature: bytesutil.PadTo([]byte("sig1"), 96),
CommitteeBits: primitives.NewAttestationCommitteeBits(),
},
{
AggregationBits: bitfield.Bitlist{0x01},
Data: &eth.AttestationData{
Slot: 456,
CommitteeIndex: 456,
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
Source: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
Target: &eth.Checkpoint{
Epoch: 456,
Root: bytesutil.PadTo([]byte("root2"), 32),
},
},
Signature: bytesutil.PadTo([]byte("sig2"), 96),
CommitteeBits: primitives.NewAttestationCommitteeBits(),
},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
b := util.NewBeaconBlock()
b.Block.Body.Attestations = preElectraAtts
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
s.GetBlockAttestations(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data))
atts := make([]*eth.Attestation, len(b.Block.Body.Attestations))
for i, a := range resp.Data {
atts[i], err = a.ToConsensus()
require.NoError(t, err)
}
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
})
t.Run("execution optimistic", func(t *testing.T) {
b := util.NewBeaconBlockBellatrix()
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
mockChainService := &chainMock.ChainService{
OptimisticRoots: map[[32]byte]bool{r: true},
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
bb := util.NewBeaconBlockBellatrix()
bb.Block.Body.Attestations = preElectraAtts
bsb, err := blocks.NewSignedBeaconBlock(bb)
require.NoError(t, err)
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
eb := util.NewBeaconBlockElectra()
eb.Block.Body.Attestations = electraAtts
esb, err := blocks.NewSignedBeaconBlock(eb)
require.NoError(t, err)
s.GetBlockAttestations(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
b := util.NewBeaconBlock()
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
t.Run("v1", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
mockChainService := &chainMock.ChainService{
FinalizedRoots: map[[32]byte]bool{},
}
t.Run("true", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestations(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data))
atts := make([]*eth.Attestation, len(b.Block.Body.Attestations))
for i, a := range resp.Data {
atts[i], err = a.ToConsensus()
require.NoError(t, err)
}
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
})
t.Run("false", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
t.Run("execution-optimistic", func(t *testing.T) {
r, err := bsb.Block().HashTreeRoot()
require.NoError(t, err)
mockChainService := &chainMock.ChainService{
OptimisticRoots: map[[32]byte]bool{r: true},
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
Blocker: &testutil.MockBlocker{BlockToReturn: bsb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -658,7 +662,194 @@ func TestGetBlockAttestations(t *testing.T) {
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, false, resp.ExecutionOptimistic)
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
t.Run("true", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestations(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
})
t.Run("false", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestations(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, false, resp.ExecutionOptimistic)
})
})
})
t.Run("V2", func(t *testing.T) {
t.Run("ok-pre-electra", func(t *testing.T) {
mockChainService := &chainMock.ChainService{
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestationsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsV2Response{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
var attStructs []structs.Attestation
require.NoError(t, json.Unmarshal(resp.Data, &attStructs))
atts := make([]*eth.Attestation, len(attStructs))
for i, attStruct := range attStructs {
atts[i], err = attStruct.ToConsensus()
require.NoError(t, err)
}
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
assert.Equal(t, "phase0", resp.Version)
})
t.Run("ok-post-electra", func(t *testing.T) {
mockChainService := &chainMock.ChainService{
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: esb},
}
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: esb}
s.Blocker = mockBlockFetcher
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestationsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsV2Response{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
var attStructs []structs.AttestationElectra
require.NoError(t, json.Unmarshal(resp.Data, &attStructs))
atts := make([]*eth.AttestationElectra, len(attStructs))
for i, attStruct := range attStructs {
atts[i], err = attStruct.ToConsensus()
require.NoError(t, err)
}
assert.DeepEqual(t, eb.Block.Body.Attestations, atts)
assert.Equal(t, "electra", resp.Version)
})
t.Run("execution-optimistic", func(t *testing.T) {
r, err := bsb.Block().HashTreeRoot()
require.NoError(t, err)
mockChainService := &chainMock.ChainService{
OptimisticRoots: map[[32]byte]bool{r: true},
FinalizedRoots: map[[32]byte]bool{},
}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: bsb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestationsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsV2Response{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
assert.Equal(t, "bellatrix", resp.Version)
})
t.Run("finalized", func(t *testing.T) {
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
t.Run("true", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestationsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsV2Response{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
assert.Equal(t, "phase0", resp.Version)
})
t.Run("false", func(t *testing.T) {
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
s := &Server{
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetBlockAttestationsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetBlockAttestationsV2Response{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, false, resp.ExecutionOptimistic)
assert.Equal(t, "phase0", resp.Version)
})
})
})
}

View File

@@ -63,7 +63,46 @@ func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
}
func TestLightClientHandler_GetLightClientBootstrap_Bellatrix(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix()
slot := l.State.Slot()
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
require.NoError(t, err)
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
s := &Server{
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
slot: l.State,
}},
Blocker: mockBlocker,
HeadFetcher: mockChainService,
}
request := httptest.NewRequest("GET", "http://foo.com/", nil)
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientBootstrap(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp structs.LightClientBootstrapResponse
err = json.Unmarshal(writer.Body.Bytes(), &resp)
require.NoError(t, err)
var respHeader structs.LightClientHeader
err = json.Unmarshal(resp.Data.Header, &respHeader)
require.NoError(t, err)
require.Equal(t, "bellatrix", resp.Version)
blockHeader, err := l.Block.Header()
require.NoError(t, err)
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
require.NotNil(t, resp.Data.CurrentSyncCommittee)
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
}
func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) {

View File

@@ -69,6 +69,7 @@ type Flags struct {
DisableResourceManager bool // Disables running the node with libp2p's resource manager.
DisableStakinContractCheck bool // Disables check for deposit contract when proposing blocks
EnableHashtree bool // Enables usage of the hashtree library for hashing
EnableVerboseSigVerification bool // EnableVerboseSigVerification specifies whether to verify individual signature if batch verification fails
PrepareAllPayloads bool // PrepareAllPayloads informs the engine to prepare a block on every slot.
@@ -78,6 +79,8 @@ type Flags struct {
SaveInvalidBlock bool // SaveInvalidBlock saves invalid block to temp.
SaveInvalidBlob bool // SaveInvalidBlob saves invalid blob to temp.
EnableDiscoveryReboot bool // EnableDiscoveryReboot allows the node to have its local listener to be rebooted in the event of discovery issues.
// KeystoreImportDebounceInterval specifies the time duration the validator waits to reload new keys if they have
// changed on disk. This feature is for advanced use cases only.
KeystoreImportDebounceInterval time.Duration
@@ -231,6 +234,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error {
logEnabled(enableFullSSZDataLogging)
cfg.EnableFullSSZDataLogging = true
}
if ctx.IsSet(enableHashtree.Name) {
logEnabled(enableHashtree)
cfg.EnableHashtree = true
}
cfg.EnableVerboseSigVerification = true
if ctx.IsSet(disableVerboseSigVerification.Name) {
logEnabled(disableVerboseSigVerification)
@@ -260,6 +267,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error {
logEnabled(DisableCommitteeAwarePacking)
cfg.DisableCommitteeAwarePacking = true
}
if ctx.IsSet(EnableDiscoveryReboot.Name) {
logEnabled(EnableDiscoveryReboot)
cfg.EnableDiscoveryReboot = true
}
cfg.AggregateIntervals = [3]time.Duration{aggregateFirstInterval.Value, aggregateSecondInterval.Value, aggregateThirdInterval.Value}
Init(cfg)

View File

@@ -135,6 +135,10 @@ var (
Name: "enable-beacon-rest-api",
Usage: "(Experimental): Enables of the beacon REST API when querying a beacon node.",
}
enableHashtree = &cli.BoolFlag{
Name: "enable-hashtree",
Usage: "(Experimental): Enables the hashthree hashing library.",
}
disableVerboseSigVerification = &cli.BoolFlag{
Name: "disable-verbose-sig-verification",
Usage: "Disables identifying invalid signatures if batch verification fails when processing block.",
@@ -170,6 +174,10 @@ var (
Name: "disable-committee-aware-packing",
Usage: "Changes the attestation packing algorithm to one that is not aware of attesting committees.",
}
EnableDiscoveryReboot = &cli.BoolFlag{
Name: "enable-discovery-reboot",
Usage: "Experimental: Enables the discovery listener to rebooted in the event of connectivity issues.",
}
)
// devModeFlags holds list of flags that are set when development mode is on.
@@ -211,6 +219,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c
disablePeerScorer,
disableBroadcastSlashingFlag,
enableSlasherFlag,
enableHashtree,
enableHistoricalSpaceRepresentation,
disableStakinContractCheck,
SaveFullExecutionPayloads,
@@ -227,6 +236,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c
BlobSaveFsync,
EnableQUIC,
DisableCommitteeAwarePacking,
EnableDiscoveryReboot,
}...)...)
// E2EBeaconChainFlags contains a list of the beacon chain feature flags to be tested in E2E.

View File

@@ -19,6 +19,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/state/stateutil:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",

View File

@@ -3,6 +3,7 @@ package blocks
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/gohashtree"
"github.com/prysmaticlabs/prysm/v5/config/features"
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
@@ -39,7 +40,11 @@ func VerifyKZGInclusionProof(blob ROBlob) error {
return errInvalidBodyRoot
}
chunks := makeChunk(blob.KzgCommitment)
gohashtree.HashChunks(chunks, chunks)
if features.Get().EnableHashtree {
hashtree.HashChunks(chunks, chunks)
} else {
gohashtree.HashChunks(chunks, chunks)
}
verified := trie.VerifyMerkleProof(root, chunks[0][:], blob.Index+KZGOffset, blob.CommitmentInclusionProof)
if !verified {
return errInvalidInclusionProof

View File

@@ -5,7 +5,11 @@ go_library(
srcs = ["hashtree.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/crypto/hash/htr",
visibility = ["//visibility:public"],
deps = ["@com_github_prysmaticlabs_gohashtree//:go_default_library"],
deps = [
"//config/features:go_default_library",
"@com_github_prysmaticlabs_gohashtree//:go_default_library",
"@com_github_prysmaticlabs_hashtree//:go_default_library",
],
)
go_test(

View File

@@ -5,13 +5,20 @@ import (
"sync"
"github.com/prysmaticlabs/gohashtree"
"github.com/prysmaticlabs/hashtree"
"github.com/prysmaticlabs/prysm/v5/config/features"
)
const minSliceSizeToParallelize = 5000
func hashParallel(inputList [][32]byte, outputList [][32]byte, wg *sync.WaitGroup) {
defer wg.Done()
err := gohashtree.Hash(outputList, inputList)
var err error
if features.Get().EnableHashtree {
err = hashtree.Hash(outputList, inputList)
} else {
err = gohashtree.Hash(outputList, inputList)
}
if err != nil {
panic(err)
}
@@ -38,7 +45,12 @@ func VectorizedSha256(inputList [][32]byte) [][32]byte {
for j := 0; j < n; j++ {
go hashParallel(inputList[j*2*groupSize:(j+1)*2*groupSize], outputList[j*groupSize:], &wg)
}
err := gohashtree.Hash(outputList[n*groupSize:], inputList[n*2*groupSize:])
var err error
if features.Get().EnableHashtree {
err = hashtree.Hash(outputList[n*groupSize:], inputList[n*2*groupSize:])
} else {
err = gohashtree.Hash(outputList[n*groupSize:], inputList[n*2*groupSize:])
}
if err != nil {
panic(err)
}

View File

@@ -4810,3 +4810,13 @@ def prysm_deps():
build_file = "//third_party:blst/blst.BUILD",
sha256 = "132124c074e59ead77e1828cc54b587a182ea67b781b72198e802af4696d78fe",
)
http_archive(
name = "com_github_prysmaticlabs_hashtree",
urls = [
"https://github.com/prysmaticlabs/hashtree/archive/refs/tags/v0.2.0.tar.gz",
],
strip_prefix = "hashtree-0.2.0",
build_file = "//third_party:hashtree/hashtree.BUILD",
sha256 = "26e6e14712040be81b5c9c08dc0ad2d48e03f2f9032806a76e98e438af10c1e0",
)

View File

@@ -344,6 +344,91 @@ func (l *TestLightClient) SetupTestAltair() *TestLightClient {
return l
}
func (l *TestLightClient) SetupTestBellatrix() *TestLightClient {
ctx := context.Background()
slot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
attestedState, err := NewBeaconStateBellatrix()
require.NoError(l.T, err)
err = attestedState.SetSlot(slot)
require.NoError(l.T, err)
finalizedBlock, err := blocks.NewSignedBeaconBlock(NewBeaconBlockBellatrix())
require.NoError(l.T, err)
finalizedBlock.SetSlot(1)
finalizedHeader, err := finalizedBlock.Header()
require.NoError(l.T, err)
finalizedRoot, err := finalizedHeader.Header.HashTreeRoot()
require.NoError(l.T, err)
require.NoError(l.T, attestedState.SetFinalizedCheckpoint(&ethpb.Checkpoint{
Epoch: params.BeaconConfig().BellatrixForkEpoch - 10,
Root: finalizedRoot[:],
}))
parent := NewBeaconBlockBellatrix()
parent.Block.Slot = slot
signedParent, err := blocks.NewSignedBeaconBlock(parent)
require.NoError(l.T, err)
parentHeader, err := signedParent.Header()
require.NoError(l.T, err)
attestedHeader := parentHeader.Header
err = attestedState.SetLatestBlockHeader(attestedHeader)
require.NoError(l.T, err)
attestedStateRoot, err := attestedState.HashTreeRoot(ctx)
require.NoError(l.T, err)
// get a new signed block so the root is updated with the new state root
parent.Block.StateRoot = attestedStateRoot[:]
signedParent, err = blocks.NewSignedBeaconBlock(parent)
require.NoError(l.T, err)
state, err := NewBeaconStateBellatrix()
require.NoError(l.T, err)
err = state.SetSlot(slot)
require.NoError(l.T, err)
parentRoot, err := signedParent.Block().HashTreeRoot()
require.NoError(l.T, err)
block := NewBeaconBlockBellatrix()
block.Block.Slot = slot
block.Block.ParentRoot = parentRoot[:]
for i := uint64(0); i < params.BeaconConfig().MinSyncCommitteeParticipants; i++ {
block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true)
}
signedBlock, err := blocks.NewSignedBeaconBlock(block)
require.NoError(l.T, err)
h, err := signedBlock.Header()
require.NoError(l.T, err)
err = state.SetLatestBlockHeader(h.Header)
require.NoError(l.T, err)
stateRoot, err := state.HashTreeRoot(ctx)
require.NoError(l.T, err)
// get a new signed block so the root is updated with the new state root
block.Block.StateRoot = stateRoot[:]
signedBlock, err = blocks.NewSignedBeaconBlock(block)
require.NoError(l.T, err)
l.State = state
l.AttestedState = attestedState
l.Block = signedBlock
l.Ctx = ctx
l.FinalizedBlock = finalizedBlock
l.AttestedBlock = signedParent
return l
}
func (l *TestLightClient) SetupTestDeneb(blinded bool) *TestLightClient {
ctx := context.Background()

63
third_party/hashtree/hashtree.BUILD vendored Normal file
View File

@@ -0,0 +1,63 @@
load("@prysm//tools/go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_test")
go_library(
name = "go_default_library",
srcs = [
"bindings.go",
"sha256_1_generic.go",
"bindings_amd64.go",
"bindings_arm64.go",
"wrapper_arm64.s",
"wrapper_linux_amd64.s",
"wrapper_windows_amd64.s",
],
deps = select({
"@io_bazel_rules_go//go/platform:amd64": ["@com_github_klauspost_cpuid_v2//:cpuid"],
"@io_bazel_rules_go//go/platform:arm64": ["@com_github_klauspost_cpuid_v2//:cpuid"],
"//conditions:default": [],
}),
cgo = True,
cdeps = [":hashtree"],
importpath = "github.com/prysmaticlabs/hashtree",
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"bindings_test.go",
],
embed = [":go_default_library"],
# c-archive creates a statically-linked c-archive file instead of
# a go test binary. So, it won't run, but it does compile.
linkmode = "c-archive",
)
cc_library(
name = "hashtree",
srcs = [
"src/hashtree.c",
"src/sha256_sse_x1.S",
"src/sha256_avx_x1.S",
"src/sha256_avx_x4.S",
"src/sha256_avx_x8.S",
"src/sha256_avx_x16.S",
"src/sha256_shani.S",
"src/sha256_armv8_crypto.S",
"src/sha256_armv8_neon_x1.S",
"src/sha256_armv8_neon_x4.S",
],
hdrs = [
"src/hashtree.h",
],
copts = [
"-g -Wall -Werror -fpic",
"-O2",
"-Isrc",
"-fno-integrated-as",
],
visibility = ["//visibility:public"],
linkstatic = True,
)