mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 04:54:05 -05:00
**What type of PR is this?** Other **What does this PR do? Why is it needed?** This pull request removes `NUMBER_OF_COLUMNS` and `MAX_CELLS_IN_EXTENDED_MATRIX` configuration. **Other notes for review** Please read commit by commit, with commit messages. **Acknowledgements** - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [x] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description to this PR with sufficient context for reviewers to understand this PR.
4671 lines
175 KiB
Go
4671 lines
175 KiB
Go
package beacon
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/OffchainLabs/go-bitfield"
|
|
"github.com/OffchainLabs/prysm/v7/api"
|
|
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
|
|
chainMock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
|
|
dbTest "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
|
|
doublylinkedtree "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/doubly-linked-tree"
|
|
mockp2p "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
|
|
rpctesting "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/shared/testing"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/lookup"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/testutil"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
|
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
|
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
"github.com/OffchainLabs/prysm/v7/network/httputil"
|
|
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
|
mock2 "github.com/OffchainLabs/prysm/v7/testing/mock"
|
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/pkg/errors"
|
|
ssz "github.com/prysmaticlabs/fastssz"
|
|
logTest "github.com/sirupsen/logrus/hooks/test"
|
|
"github.com/stretchr/testify/mock"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
func fillDBTestBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (*eth.SignedBeaconBlock, []*eth.BeaconBlockContainer) {
|
|
parentRoot := [32]byte{1, 2, 3}
|
|
genBlk := util.NewBeaconBlock()
|
|
genBlk.Block.ParentRoot = parentRoot[:]
|
|
root, err := genBlk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, beaconDB, genBlk)
|
|
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, root))
|
|
|
|
count := primitives.Slot(100)
|
|
blks := make([]interfaces.ReadOnlySignedBeaconBlock, count)
|
|
blkContainers := make([]*eth.BeaconBlockContainer, count)
|
|
for i := range count {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = i
|
|
b.Block.ParentRoot = bytesutil.PadTo([]byte{uint8(i)}, 32)
|
|
root, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
blks[i], err = blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
blkContainers[i] = ð.BeaconBlockContainer{
|
|
Block: ð.BeaconBlockContainer_Phase0Block{Phase0Block: b},
|
|
BlockRoot: root[:],
|
|
}
|
|
}
|
|
require.NoError(t, beaconDB.SaveBlocks(ctx, blks))
|
|
headRoot := bytesutil.ToBytes32(blkContainers[len(blks)-1].BlockRoot)
|
|
summary := ð.StateSummary{
|
|
Root: headRoot[:],
|
|
Slot: blkContainers[len(blks)-1].Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block.Block.Slot,
|
|
}
|
|
require.NoError(t, beaconDB.SaveStateSummary(ctx, summary))
|
|
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, headRoot))
|
|
return genBlk, blkContainers
|
|
}
|
|
|
|
func TestGetBlockV2(t *testing.T) {
|
|
t.Run("Unsycned Block", func(t *testing.T) {
|
|
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: nil}
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "123552314")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusNotFound, writer.Code)
|
|
})
|
|
t.Run("phase0", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Phase0), resp.Version)
|
|
sbb := &structs.SignedBeaconBlock{Message: &structs.BeaconBlock{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetPhase0()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("altair", func(t *testing.T) {
|
|
b := util.NewBeaconBlockAltair()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Altair), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockAltair{Message: &structs.BeaconBlockAltair{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetAltair()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("bellatrix", func(t *testing.T) {
|
|
b := util.NewBeaconBlockBellatrix()
|
|
b.Block.Slot = 123
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Bellatrix), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockBellatrix{Message: &structs.BeaconBlockBellatrix{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetBellatrix()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("capella", func(t *testing.T) {
|
|
b := util.NewBeaconBlockCapella()
|
|
b.Block.Slot = 123
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Capella), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockCapella{Message: &structs.BeaconBlockCapella{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetCapella()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("deneb", func(t *testing.T) {
|
|
b := util.NewBeaconBlockDeneb()
|
|
b.Block.Slot = 123
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Deneb), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockDeneb{Message: &structs.BeaconBlockDeneb{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("electra", func(t *testing.T) {
|
|
b := util.NewBeaconBlockElectra()
|
|
b.Block.Slot = 123
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Electra), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockElectra{Message: &structs.BeaconBlockElectra{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("fulu", func(t *testing.T) {
|
|
b := util.NewBeaconBlockFulu()
|
|
b.Block.Slot = 123
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Fulu), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockFulu{Message: &structs.BeaconBlockElectra{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
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,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
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("true", func(t *testing.T) {
|
|
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(r[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
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: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(r[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, false, resp.Finalized)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetBlockSSZV2(t *testing.T) {
|
|
t.Run("phase0", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Phase0), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("altair", func(t *testing.T) {
|
|
b := util.NewBeaconBlockAltair()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Altair), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("bellatrix", func(t *testing.T) {
|
|
b := util.NewBeaconBlockBellatrix()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Bellatrix), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("capella", func(t *testing.T) {
|
|
b := util.NewBeaconBlockCapella()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Capella), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("deneb", func(t *testing.T) {
|
|
b := util.NewBeaconBlockDeneb()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Deneb), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("electra", func(t *testing.T) {
|
|
b := util.NewBeaconBlockElectra()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Electra), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("fulu", func(t *testing.T) {
|
|
b := util.NewBeaconBlockFulu()
|
|
b.Block.Slot = 123
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockV2(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Fulu), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
}
|
|
|
|
func TestGetBlockAttestationsV2(t *testing.T) {
|
|
preElectraAtts := []*eth.Attestation{
|
|
{
|
|
AggregationBits: bitfield.Bitlist{0x00},
|
|
Data: ð.AttestationData{
|
|
Slot: 123,
|
|
CommitteeIndex: 123,
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
|
Source: ð.Checkpoint{
|
|
Epoch: 123,
|
|
Root: bytesutil.PadTo([]byte("root1"), 32),
|
|
},
|
|
Target: ð.Checkpoint{
|
|
Epoch: 123,
|
|
Root: bytesutil.PadTo([]byte("root1"), 32),
|
|
},
|
|
},
|
|
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
|
},
|
|
{
|
|
AggregationBits: bitfield.Bitlist{0x01},
|
|
Data: ð.AttestationData{
|
|
Slot: 456,
|
|
CommitteeIndex: 456,
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
|
Source: ð.Checkpoint{
|
|
Epoch: 456,
|
|
Root: bytesutil.PadTo([]byte("root2"), 32),
|
|
},
|
|
Target: ð.Checkpoint{
|
|
Epoch: 456,
|
|
Root: bytesutil.PadTo([]byte("root2"), 32),
|
|
},
|
|
},
|
|
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
|
},
|
|
}
|
|
electraAtts := []*eth.AttestationElectra{
|
|
{
|
|
AggregationBits: bitfield.Bitlist{0x00},
|
|
Data: ð.AttestationData{
|
|
Slot: 123,
|
|
CommitteeIndex: 123,
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
|
Source: ð.Checkpoint{
|
|
Epoch: 123,
|
|
Root: bytesutil.PadTo([]byte("root1"), 32),
|
|
},
|
|
Target: ð.Checkpoint{
|
|
Epoch: 123,
|
|
Root: bytesutil.PadTo([]byte("root1"), 32),
|
|
},
|
|
},
|
|
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
|
CommitteeBits: primitives.NewAttestationCommitteeBits(),
|
|
},
|
|
{
|
|
AggregationBits: bitfield.Bitlist{0x01},
|
|
Data: ð.AttestationData{
|
|
Slot: 456,
|
|
CommitteeIndex: 456,
|
|
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
|
Source: ð.Checkpoint{
|
|
Epoch: 456,
|
|
Root: bytesutil.PadTo([]byte("root2"), 32),
|
|
},
|
|
Target: ð.Checkpoint{
|
|
Epoch: 456,
|
|
Root: bytesutil.PadTo([]byte("root2"), 32),
|
|
},
|
|
},
|
|
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
|
CommitteeBits: primitives.NewAttestationCommitteeBits(),
|
|
},
|
|
}
|
|
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Body.Attestations = preElectraAtts
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
bb := util.NewBeaconBlockBellatrix()
|
|
bb.Block.Body.Attestations = preElectraAtts
|
|
bsb, err := blocks.NewSignedBeaconBlock(bb)
|
|
require.NoError(t, err)
|
|
|
|
eb := util.NewBeaconBlockElectra()
|
|
eb.Block.Body.Attestations = electraAtts
|
|
esb, err := blocks.NewSignedBeaconBlock(eb)
|
|
require.NoError(t, err)
|
|
|
|
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)
|
|
})
|
|
})
|
|
|
|
t.Run("empty-attestations", func(t *testing.T) {
|
|
t.Run("pre-electra", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Body.Attestations = []*eth.Attestation{} // Explicitly set empty attestations
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
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))
|
|
// Ensure data is "[]", not null
|
|
require.NotNil(t, resp.Data)
|
|
assert.Equal(t, string(json.RawMessage("[]")), string(resp.Data))
|
|
})
|
|
|
|
t.Run("electra", func(t *testing.T) {
|
|
eb := util.NewBeaconBlockFulu()
|
|
eb.Block.Body.Attestations = []*eth.AttestationElectra{} // Explicitly set empty attestations
|
|
esb, err := blocks.NewSignedBeaconBlock(eb)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: esb},
|
|
}
|
|
|
|
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))
|
|
|
|
// Ensure data is "[]", not null
|
|
require.NotNil(t, resp.Data)
|
|
assert.Equal(t, string(json.RawMessage("[]")), string(resp.Data))
|
|
assert.Equal(t, "fulu", resp.Version)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetBlindedBlock(t *testing.T) {
|
|
t.Run("phase0", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
FinalizationFetcher: &chainMock.ChainService{},
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Phase0), resp.Version)
|
|
sbb := &structs.SignedBeaconBlock{Message: &structs.BeaconBlock{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetPhase0()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("altair", func(t *testing.T) {
|
|
b := util.NewBeaconBlockAltair()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
FinalizationFetcher: &chainMock.ChainService{},
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Altair), resp.Version)
|
|
sbb := &structs.SignedBeaconBlockAltair{Message: &structs.BeaconBlockAltair{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetAltair()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("bellatrix", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockBellatrix()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Bellatrix), resp.Version)
|
|
sbb := &structs.SignedBlindedBeaconBlockBellatrix{Message: &structs.BlindedBeaconBlockBellatrix{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetBlindedBellatrix()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("capella", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockCapella()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Capella), resp.Version)
|
|
sbb := &structs.SignedBlindedBeaconBlockCapella{Message: &structs.BlindedBeaconBlockCapella{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
genericBlk, err := sbb.ToGeneric()
|
|
require.NoError(t, err)
|
|
blk := genericBlk.GetBlindedCapella()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("deneb", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockDeneb()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Deneb), resp.Version)
|
|
sbb := &structs.SignedBlindedBeaconBlockDeneb{Message: &structs.BlindedBeaconBlockDeneb{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("electra", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockElectra()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Electra), resp.Version)
|
|
sbb := &structs.SignedBlindedBeaconBlockElectra{Message: &structs.BlindedBeaconBlockElectra{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("fulu", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockFulu()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, version.String(version.Fulu), resp.Version)
|
|
sbb := &structs.SignedBlindedBeaconBlockFulu{Message: &structs.BlindedBeaconBlockFulu{}}
|
|
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
|
sbb.Signature = resp.Data.Signature
|
|
blk, err := sbb.ToConsensus()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, blk, b)
|
|
})
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockBellatrix()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
r, err := sb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{
|
|
OptimisticRoots: map[[32]byte]bool{r: true},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
OptimisticModeFetcher: mockChainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
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)
|
|
root, err := sb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{root: true},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(root[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.Finalized)
|
|
})
|
|
t.Run("not finalized", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
root, err := sb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{root: false},
|
|
}
|
|
s := &Server{
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(root[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockV2Response{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, false, resp.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetBlindedBlockSSZ(t *testing.T) {
|
|
t.Run("phase0", func(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Phase0), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("Altair", func(t *testing.T) {
|
|
b := util.NewBeaconBlockAltair()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Altair), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("Bellatrix", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockBellatrix()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Bellatrix), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("Capella", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockCapella()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Capella), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
t.Run("Deneb", func(t *testing.T) {
|
|
b := util.NewBlindedBeaconBlockDeneb()
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{
|
|
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
request.Header.Set("Accept", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlindedBlock(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
assert.Equal(t, version.String(version.Deneb), writer.Header().Get(api.VersionHeader))
|
|
sszExpected, err := b.MarshalSSZ()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
|
})
|
|
}
|
|
|
|
func TestVersionHeaderFromRequest(t *testing.T) {
|
|
t.Run("Fulu block contents returns fulu header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.FuluForkEpoch = 7
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockContentsFulu
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.FuluBlockContents), &signedblock))
|
|
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().FuluForkEpoch))
|
|
newContents, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newContents)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Fulu), versionHead)
|
|
})
|
|
t.Run("Blinded Fulu block returns fulu header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.FuluForkEpoch = 7
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBlindedBeaconBlockFulu
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().FuluForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Fulu), versionHead)
|
|
})
|
|
t.Run("Electra block contents returns electra header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.ElectraForkEpoch = 6
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockContentsElectra
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &signedblock))
|
|
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch))
|
|
newContents, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newContents)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Electra), versionHead)
|
|
})
|
|
t.Run("Blinded Electra block returns electra header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.ElectraForkEpoch = 6
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBlindedBeaconBlockElectra
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Electra), versionHead)
|
|
})
|
|
t.Run("Deneb block contents returns deneb header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.DenebForkEpoch = 5
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockContentsDeneb
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.DenebBlockContents), &signedblock))
|
|
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().DenebForkEpoch))
|
|
newContents, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newContents)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Deneb), versionHead)
|
|
})
|
|
t.Run("Blinded Deneb block returns Deneb header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.DenebForkEpoch = 5
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBlindedBeaconBlockDeneb
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().DenebForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Deneb), versionHead)
|
|
})
|
|
t.Run("Capella block returns Capella header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.CapellaForkEpoch = 4
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockCapella
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.CapellaBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().CapellaForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Capella), versionHead)
|
|
})
|
|
t.Run("Blinded Capella block returns Capella header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.CapellaForkEpoch = 4
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBlindedBeaconBlockCapella
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedCapellaBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().CapellaForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Capella), versionHead)
|
|
})
|
|
t.Run("Bellatrix block returns capella header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.BellatrixForkEpoch = 3
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockBellatrix
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BellatrixBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().BellatrixForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Bellatrix), versionHead)
|
|
})
|
|
t.Run("Blinded Capella block returns Capella header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.BellatrixForkEpoch = 3
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBlindedBeaconBlockBellatrix
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().BellatrixForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Bellatrix), versionHead)
|
|
})
|
|
t.Run("Altair block returns capella header", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
cfg.AltairForkEpoch = 2
|
|
params.OverrideBeaconConfig(cfg)
|
|
params.SetupTestConfigCleanup(t)
|
|
var signedblock *structs.SignedBeaconBlockAltair
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock))
|
|
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().AltairForkEpoch))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Altair), versionHead)
|
|
})
|
|
t.Run("Phase0 block returns capella header", func(t *testing.T) {
|
|
var signedblock *structs.SignedBeaconBlock
|
|
require.NoError(t, json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock))
|
|
newBlock, err := json.Marshal(signedblock)
|
|
require.NoError(t, err)
|
|
versionHead, err := versionHeaderFromRequest(newBlock)
|
|
require.NoError(t, err)
|
|
require.Equal(t, version.String(version.Phase0), versionHead)
|
|
})
|
|
t.Run("Malformed json returns error unable to peek slot from block contents", func(t *testing.T) {
|
|
malformedJSON := []byte(`{"age": 30,}`)
|
|
_, err := versionHeaderFromRequest(malformedJSON)
|
|
require.ErrorContains(t, "unable to peek slot", err)
|
|
})
|
|
}
|
|
|
|
func TestPublishBlockV2(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
t.Run("Phase 0", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Phase0)
|
|
var signedblock *structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockFromConsensus(block.Phase0.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.Phase0Block)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Altair", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Altair)
|
|
var signedblock *structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockAltairFromConsensus(block.Altair.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.AltairBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Altair))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Bellatrix", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Bellatrix)
|
|
converted, err := structs.BeaconBlockBellatrixFromConsensus(block.Bellatrix.Block)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBeaconBlockBellatrix
|
|
err = json.Unmarshal([]byte(rpctesting.BellatrixBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Capella", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Capella)
|
|
converted, err := structs.BeaconBlockCapellaFromConsensus(block.Capella.Block)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBeaconBlockCapella
|
|
err = json.Unmarshal([]byte(rpctesting.CapellaBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.CapellaBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Deneb", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Deneb)
|
|
converted, err := structs.SignedBeaconBlockContentsDenebFromConsensus(block.Deneb)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBeaconBlockContentsDeneb
|
|
err = json.Unmarshal([]byte(rpctesting.DenebBlockContents), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.DenebBlockContents)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Deneb))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Electra", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Electra)
|
|
converted, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBeaconBlockContentsElectra
|
|
err = json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.ElectraBlockContents)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Fulu", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Fulu)
|
|
converted, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBeaconBlockContentsFulu
|
|
err = json.Unmarshal([]byte(rpctesting.FuluBlockContents), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.FuluBlockContents)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Fulu))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("invalid block", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedBellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String())
|
|
})
|
|
t.Run("wrong version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Capella)), writer.Body.String())
|
|
})
|
|
t.Run("missing version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.CapellaBlock)))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, api.VersionHeader+" header is required", writer.Body.String())
|
|
})
|
|
t.Run("syncing", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{}
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
|
HeadFetcher: chainService,
|
|
TimeFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte("foo")))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
|
|
assert.StringContains(t, "Beacon node is currently syncing and not serving request on that endpoint", writer.Body.String())
|
|
})
|
|
}
|
|
|
|
func TestPublishBlockV2SSZ(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
t.Run("Phase 0", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Phase0)
|
|
var signedblock *structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockFromConsensus(block.Phase0.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetPhase0().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Altair", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Altair)
|
|
var signedblock *structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockAltairFromConsensus(block.Altair.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetAltair().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Altair))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Bellatrix", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_Bellatrix)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
var blk structs.SignedBeaconBlockBellatrix
|
|
err := json.Unmarshal([]byte(rpctesting.BellatrixBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBellatrix().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Capella", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_Capella)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockCapella
|
|
err := json.Unmarshal([]byte(rpctesting.CapellaBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetCapella().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Deneb", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_Deneb)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockContentsDeneb
|
|
err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetDeneb().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Deneb))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Electra", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_Electra)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockContentsElectra
|
|
err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetElectra().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Fulu", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_Fulu)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockContentsFulu
|
|
err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetFulu().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Fulu))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("invalid block", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockBellatrix
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedBellatrix().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String())
|
|
})
|
|
t.Run("wrong version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockBellatrix
|
|
err := json.Unmarshal([]byte(rpctesting.BellatrixBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBellatrix().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String())
|
|
})
|
|
t.Run("missing version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.CapellaBlock)))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, api.VersionHeader+" header is required", writer.Body.String())
|
|
})
|
|
t.Run("syncing", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{}
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
|
HeadFetcher: chainService,
|
|
TimeFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte("foo")))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
|
|
assert.StringContains(t, "Beacon node is currently syncing and not serving request on that endpoint", writer.Body.String())
|
|
})
|
|
}
|
|
|
|
func TestPublishBlindedBlockV2(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
t.Run("Phase 0", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Phase0)
|
|
var signedblock *structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockFromConsensus(block.Phase0.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.Phase0Block)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Altair", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Altair)
|
|
var signedblock *structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockAltairFromConsensus(block.Altair.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.AltairBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Altair))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Blinded Bellatrix", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedBellatrix)
|
|
converted, err := structs.BlindedBeaconBlockBellatrixFromConsensus(block.BlindedBellatrix.Block)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBlindedBeaconBlockBellatrix
|
|
err = json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedBellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Blinded Capella", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedCapella)
|
|
converted, err := structs.BlindedBeaconBlockCapellaFromConsensus(block.BlindedCapella.Block)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBlindedBeaconBlockCapella
|
|
err = json.Unmarshal([]byte(rpctesting.BlindedCapellaBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedCapellaBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Blinded Deneb", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedDeneb)
|
|
converted, err := structs.BlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb.Message)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBlindedBeaconBlockDeneb
|
|
err = json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedDenebBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Deneb))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Blinded Electra", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedElectra)
|
|
converted, err := structs.BlindedBeaconBlockElectraFromConsensus(block.BlindedElectra.Message)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBlindedBeaconBlockElectra
|
|
err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedElectraBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Blinded Fulu", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedFulu)
|
|
converted, err := structs.BlindedBeaconBlockFuluFromConsensus(block.BlindedFulu.Message)
|
|
require.NoError(t, err)
|
|
var signedblock *structs.SignedBlindedBeaconBlockFulu
|
|
err = json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, converted, signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedFuluBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Fulu))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("invalid block", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String())
|
|
})
|
|
t.Run("wrong version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedBellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String())
|
|
})
|
|
t.Run("missing version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedCapellaBlock)))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, api.VersionHeader+" header is required", writer.Body.String())
|
|
})
|
|
t.Run("syncing", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{}
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
|
HeadFetcher: chainService,
|
|
TimeFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte("foo")))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
|
|
assert.Equal(t, true, strings.Contains(writer.Body.String(), "Beacon node is currently syncing and not serving request on that endpoint"))
|
|
})
|
|
}
|
|
|
|
func TestPublishBlindedBlockV2SSZ(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
t.Run("Phase 0", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Phase0)
|
|
var signedblock *structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockFromConsensus(block.Phase0.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlock
|
|
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetPhase0().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Phase0))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Altair", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Altair)
|
|
var signedblock *structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock)
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, structs.BeaconBlockAltairFromConsensus(block.Altair.Block), signedblock.Message)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBeaconBlockAltair
|
|
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetAltair().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Altair))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Bellatrix", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedBellatrix)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockBellatrix
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedBellatrix().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Capella", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedCapella)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockCapella
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedCapellaBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedCapella().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Deneb", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedDeneb)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockDeneb
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedDeneb().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Deneb))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Electra", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedElectra)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockElectra
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedElectra().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("Fulu", func(t *testing.T) {
|
|
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
|
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
|
|
_, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedFulu)
|
|
return ok
|
|
}))
|
|
server := &Server{
|
|
V1Alpha1ValidatorServer: v1alpha1Server,
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockFulu
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedFulu().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Fulu))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
})
|
|
t.Run("invalid block", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BellatrixBlock)))
|
|
request.Header.Set(api.VersionHeader, version.String(version.Bellatrix))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String())
|
|
})
|
|
t.Run("wrong version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
var blk structs.SignedBlindedBeaconBlockBellatrix
|
|
err := json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &blk)
|
|
require.NoError(t, err)
|
|
genericBlock, err := blk.ToGeneric()
|
|
require.NoError(t, err)
|
|
ssz, err := genericBlock.GetBlindedBellatrix().MarshalSSZ()
|
|
require.NoError(t, err)
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(ssz))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
request.Header.Set(api.VersionHeader, version.String(version.Capella))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String())
|
|
})
|
|
t.Run("missing version header", func(t *testing.T) {
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedCapellaBlock)))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
assert.StringContains(t, api.VersionHeader+" header is required", writer.Body.String())
|
|
})
|
|
t.Run("syncing", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{}
|
|
server := &Server{
|
|
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
|
HeadFetcher: chainService,
|
|
TimeFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte("foo")))
|
|
request.Header.Set("Content-Type", api.OctetStreamMediaType)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
server.PublishBlindedBlockV2(writer, request)
|
|
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
|
|
assert.StringContains(t, "Beacon node is currently syncing and not serving request on that endpoint", writer.Body.String())
|
|
})
|
|
}
|
|
|
|
func TestValidateConsensus(t *testing.T) {
|
|
ctx := t.Context()
|
|
|
|
parentState, privs := util.DeterministicGenesisState(t, params.MinimalSpecConfig().MinGenesisActiveValidatorCount)
|
|
parentBlock, err := util.GenerateFullBlock(parentState, privs, util.DefaultBlockGenConfig(), parentState.Slot())
|
|
require.NoError(t, err)
|
|
parentSbb, err := blocks.NewSignedBeaconBlock(parentBlock)
|
|
require.NoError(t, err)
|
|
st, err := transition.ExecuteStateTransition(ctx, parentState, parentSbb)
|
|
require.NoError(t, err)
|
|
block, err := util.GenerateFullBlock(st, privs, util.DefaultBlockGenConfig(), st.Slot())
|
|
require.NoError(t, err)
|
|
parentRoot, err := parentSbb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
mockChainService := &chainMock.ChainService{
|
|
State: parentState,
|
|
Root: parentRoot[:],
|
|
}
|
|
server := &Server{
|
|
Blocker: &testutil.MockBlocker{RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{parentRoot: parentSbb}},
|
|
Stater: &testutil.MockStater{StatesByRoot: map[[32]byte]state.BeaconState{bytesutil.ToBytes32(parentBlock.Block.StateRoot): parentState}},
|
|
HeadFetcher: mockChainService,
|
|
}
|
|
|
|
require.NoError(t, server.validateConsensus(ctx, ð.GenericSignedBeaconBlock{
|
|
Block: ð.GenericSignedBeaconBlock_Phase0{
|
|
Phase0: block,
|
|
},
|
|
}))
|
|
}
|
|
|
|
func TestValidateEquivocation(t *testing.T) {
|
|
t.Run("ok", func(t *testing.T) {
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(10))
|
|
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
|
require.NoError(t, err)
|
|
roblock, err := blocks.NewROBlockWithRoot(blk, bytesutil.ToBytes32([]byte("root")))
|
|
require.NoError(t, err)
|
|
fc := doublylinkedtree.New()
|
|
require.NoError(t, fc.InsertNode(t.Context(), st, roblock))
|
|
server := &Server{
|
|
ForkchoiceFetcher: &chainMock.ChainService{ForkChoiceStore: fc},
|
|
}
|
|
blk.SetSlot(st.Slot() + 1)
|
|
|
|
require.NoError(t, server.validateEquivocation(blk.Block()))
|
|
})
|
|
t.Run("block already exists", func(t *testing.T) {
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(10))
|
|
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
|
require.NoError(t, err)
|
|
blk.SetSlot(st.Slot())
|
|
roblock, err := blocks.NewROBlockWithRoot(blk, bytesutil.ToBytes32([]byte("root")))
|
|
require.NoError(t, err)
|
|
|
|
fc := doublylinkedtree.New()
|
|
require.NoError(t, fc.InsertNode(t.Context(), st, roblock))
|
|
server := &Server{
|
|
ForkchoiceFetcher: &chainMock.ChainService{ForkChoiceStore: fc},
|
|
}
|
|
err = server.validateEquivocation(blk.Block())
|
|
assert.ErrorContains(t, "already exists", err)
|
|
require.ErrorIs(t, err, errEquivocatedBlock)
|
|
})
|
|
}
|
|
|
|
func TestServer_GetBlockRoot(t *testing.T) {
|
|
beaconDB := dbTest.SetupDB(t)
|
|
ctx := t.Context()
|
|
|
|
url := "http://example.com/eth/v1/beacon/blocks/{block_id}/root"
|
|
genBlk, blkContainers := fillDBTestBlocks(ctx, t, beaconDB)
|
|
headBlock := blkContainers[len(blkContainers)-1]
|
|
t.Run("get root", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
HeadFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
|
|
root, err := genBlk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
blockID map[string]string
|
|
want string
|
|
wantErr string
|
|
wantCode int
|
|
}{
|
|
{
|
|
name: "bad formatting",
|
|
blockID: map[string]string{"block_id": "3bad0"},
|
|
wantErr: "Could not parse block ID",
|
|
wantCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "canonical slot",
|
|
blockID: map[string]string{"block_id": "30"},
|
|
want: hexutil.Encode(blkContainers[30].BlockRoot),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "head",
|
|
blockID: map[string]string{"block_id": "head"},
|
|
want: hexutil.Encode(headBlock.BlockRoot),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "finalized",
|
|
blockID: map[string]string{"block_id": "finalized"},
|
|
want: hexutil.Encode(blkContainers[64].BlockRoot),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "genesis",
|
|
blockID: map[string]string{"block_id": "genesis"},
|
|
want: hexutil.Encode(root[:]),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "genesis root",
|
|
blockID: map[string]string{"block_id": hexutil.Encode(root[:])},
|
|
want: hexutil.Encode(root[:]),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "root",
|
|
blockID: map[string]string{"block_id": hexutil.Encode(blkContainers[20].BlockRoot)},
|
|
want: hexutil.Encode(blkContainers[20].BlockRoot),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "non-existent root",
|
|
blockID: map[string]string{"block_id": hexutil.Encode(bytesutil.PadTo([]byte("hi there"), 32))},
|
|
wantErr: "Could not find block",
|
|
wantCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "slot",
|
|
blockID: map[string]string{"block_id": "40"},
|
|
want: hexutil.Encode(blkContainers[40].BlockRoot),
|
|
wantErr: "",
|
|
wantCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "no block",
|
|
blockID: map[string]string{"block_id": "105"},
|
|
wantErr: "Could not find any blocks with given slot",
|
|
wantCode: http.StatusNotFound,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
blockID := tt.blockID
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("block_id", blockID["block_id"])
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockRoot(writer, request)
|
|
assert.Equal(t, tt.wantCode, writer.Code)
|
|
resp := &structs.BlockRootResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
if tt.wantErr != "" {
|
|
require.ErrorContains(t, tt.wantErr, errors.New(writer.Body.String()))
|
|
return
|
|
}
|
|
require.NotNil(t, resp)
|
|
require.DeepEqual(t, resp.Data.Root, tt.want)
|
|
})
|
|
}
|
|
})
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
OptimisticRoots: map[[32]byte]bool{
|
|
bytesutil.ToBytes32(headBlock.BlockRoot): true,
|
|
},
|
|
}
|
|
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
HeadFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockRoot(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.BlockRootResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.DeepEqual(t, resp.ExecutionOptimistic, true)
|
|
})
|
|
t.Run("finalized", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{
|
|
bytesutil.ToBytes32(blkContainers[32].BlockRoot): true,
|
|
bytesutil.ToBytes32(blkContainers[64].BlockRoot): false,
|
|
},
|
|
}
|
|
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
HeadFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
t.Run("true", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("block_id", "32")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockRoot(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.BlockRootResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.DeepEqual(t, resp.Finalized, true)
|
|
})
|
|
t.Run("false", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("block_id", "64")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockRoot(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.BlockRootResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.DeepEqual(t, resp.Finalized, false)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetStateFork(t *testing.T) {
|
|
ctx := t.Context()
|
|
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/states/{state_id}/fork", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
request.Header.Set("Accept", "application/octet-stream")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
fillFork := func(state *eth.BeaconState) error {
|
|
state.Fork = ð.Fork{
|
|
PreviousVersion: []byte("prev"),
|
|
CurrentVersion: []byte("curr"),
|
|
Epoch: 123,
|
|
}
|
|
return nil
|
|
}
|
|
fakeState, err := util.NewBeaconState(fillFork)
|
|
require.NoError(t, err)
|
|
db := dbTest.SetupDB(t)
|
|
|
|
chainService := &chainMock.ChainService{}
|
|
server := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
|
|
server.GetStateFork(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
var stateForkReponse *structs.GetStateForkResponse
|
|
err = json.Unmarshal(writer.Body.Bytes(), &stateForkReponse)
|
|
require.NoError(t, err)
|
|
expectedFork := fakeState.Fork()
|
|
assert.Equal(t, fmt.Sprint(expectedFork.Epoch), stateForkReponse.Data.Epoch)
|
|
assert.DeepEqual(t, hexutil.Encode(expectedFork.CurrentVersion), stateForkReponse.Data.CurrentVersion)
|
|
assert.DeepEqual(t, hexutil.Encode(expectedFork.PreviousVersion), stateForkReponse.Data.PreviousVersion)
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
request = httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/states/{state_id}/fork", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
request.Header.Set("Accept", "application/octet-stream")
|
|
writer = httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
parentRoot := [32]byte{'a'}
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = parentRoot[:]
|
|
root, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, db, blk)
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
|
|
|
chainService = &chainMock.ChainService{Optimistic: true}
|
|
server = &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
server.GetStateFork(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
err = json.Unmarshal(writer.Body.Bytes(), &stateForkReponse)
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, true, stateForkReponse.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized", func(t *testing.T) {
|
|
request = httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/states/{state_id}/fork", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
request.Header.Set("Accept", "application/octet-stream")
|
|
writer = httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
parentRoot := [32]byte{'a'}
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = parentRoot[:]
|
|
root, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, db, blk)
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
|
|
|
headerRoot, err := fakeState.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
chainService = &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{
|
|
headerRoot: true,
|
|
},
|
|
}
|
|
server = &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
server.GetStateFork(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
err = json.Unmarshal(writer.Body.Bytes(), &stateForkReponse)
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, true, stateForkReponse.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetCommittees(t *testing.T) {
|
|
db := dbTest.SetupDB(t)
|
|
ctx := t.Context()
|
|
url := "http://example.com/eth/v1/beacon/states/{state_id}/committees"
|
|
|
|
var st state.BeaconState
|
|
st, _ = util.DeterministicGenesisState(t, 8192)
|
|
epoch := slots.ToEpoch(st.Slot())
|
|
|
|
chainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
|
|
t.Run("Head all committees", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, int(params.BeaconConfig().SlotsPerEpoch)*2, len(resp.Data))
|
|
for _, datum := range resp.Data {
|
|
index, err := strconv.ParseUint(datum.Index, 10, 32)
|
|
require.NoError(t, err)
|
|
slot, err := strconv.ParseUint(datum.Slot, 10, 32)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, true, index == 0 || index == 1)
|
|
assert.Equal(t, epoch, slots.ToEpoch(primitives.Slot(slot)))
|
|
}
|
|
})
|
|
t.Run("Head all committees of epoch 10", func(t *testing.T) {
|
|
query := url + "?epoch=10"
|
|
request := httptest.NewRequest(http.MethodGet, query, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
for _, datum := range resp.Data {
|
|
slot, err := strconv.ParseUint(datum.Slot, 10, 32)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, true, slot >= 320 && slot <= 351)
|
|
}
|
|
})
|
|
t.Run("Head all committees of slot 4", func(t *testing.T) {
|
|
query := url + "?slot=4"
|
|
request := httptest.NewRequest(http.MethodGet, query, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, 2, len(resp.Data))
|
|
|
|
exSlot := uint64(4)
|
|
exIndex := uint64(0)
|
|
for _, datum := range resp.Data {
|
|
slot, err := strconv.ParseUint(datum.Slot, 10, 32)
|
|
require.NoError(t, err)
|
|
index, err := strconv.ParseUint(datum.Index, 10, 32)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, epoch, slots.ToEpoch(primitives.Slot(slot)))
|
|
assert.Equal(t, exSlot, slot)
|
|
assert.Equal(t, exIndex, index)
|
|
exIndex++
|
|
}
|
|
})
|
|
t.Run("Head all committees of index 1", func(t *testing.T) {
|
|
query := url + "?index=1"
|
|
request := httptest.NewRequest(http.MethodGet, query, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, int(params.BeaconConfig().SlotsPerEpoch), len(resp.Data))
|
|
|
|
exSlot := uint64(0)
|
|
exIndex := uint64(1)
|
|
for _, datum := range resp.Data {
|
|
slot, err := strconv.ParseUint(datum.Slot, 10, 32)
|
|
require.NoError(t, err)
|
|
index, err := strconv.ParseUint(datum.Index, 10, 32)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, epoch, slots.ToEpoch(primitives.Slot(slot)))
|
|
assert.Equal(t, exSlot, slot)
|
|
assert.Equal(t, exIndex, index)
|
|
exSlot++
|
|
}
|
|
})
|
|
t.Run("Head all committees of slot 2, index 1", func(t *testing.T) {
|
|
query := url + "?slot=2&index=1"
|
|
request := httptest.NewRequest(http.MethodGet, query, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, 1, len(resp.Data))
|
|
|
|
exIndex := uint64(1)
|
|
exSlot := uint64(2)
|
|
for _, datum := range resp.Data {
|
|
index, err := strconv.ParseUint(datum.Index, 10, 32)
|
|
require.NoError(t, err)
|
|
slot, err := strconv.ParseUint(datum.Slot, 10, 32)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, epoch, slots.ToEpoch(primitives.Slot(slot)))
|
|
assert.Equal(t, exSlot, slot)
|
|
assert.Equal(t, exIndex, index)
|
|
}
|
|
})
|
|
t.Run("Execution optimistic", func(t *testing.T) {
|
|
parentRoot := [32]byte{'a'}
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = parentRoot[:]
|
|
root, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, db, blk)
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
|
|
|
chainService = &chainMock.ChainService{Optimistic: true}
|
|
s = &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
t.Run("Finalized", func(t *testing.T) {
|
|
parentRoot := [32]byte{'a'}
|
|
blk := util.NewBeaconBlock()
|
|
blk.Block.ParentRoot = parentRoot[:]
|
|
root, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, db, blk)
|
|
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
|
|
|
headerRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
chainService = &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{
|
|
headerRoot: true,
|
|
},
|
|
}
|
|
s = &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
BeaconDB: db,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, url, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetCommitteesResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, true, resp.Finalized)
|
|
})
|
|
|
|
t.Run("Invalid slot for given epoch", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
epoch string
|
|
slot string
|
|
expectMsg string
|
|
}{
|
|
{
|
|
name: "Slot after the specified epoch",
|
|
epoch: "10",
|
|
slot: "400",
|
|
expectMsg: "Slot 400 does not belong in epoch 10",
|
|
},
|
|
{
|
|
name: "Slot before the specified epoch",
|
|
epoch: "10",
|
|
slot: "300",
|
|
expectMsg: "Slot 300 does not belong in epoch 10",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
query := url + "?epoch=" + tc.epoch + "&slot=" + tc.slot
|
|
request := httptest.NewRequest(http.MethodGet, query, nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
s.GetCommittees(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
var resp struct {
|
|
Message string `json:"message"`
|
|
Code int `json:"code"`
|
|
}
|
|
err := json.Unmarshal(writer.Body.Bytes(), &resp)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectMsg, resp.Message)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetBlockHeaders(t *testing.T) {
|
|
beaconDB := dbTest.SetupDB(t)
|
|
ctx := t.Context()
|
|
|
|
_, blkContainers := fillDBTestBlocks(ctx, t, beaconDB)
|
|
headBlock := blkContainers[len(blkContainers)-1]
|
|
|
|
b1 := util.NewBeaconBlock()
|
|
b1.Block.Slot = 30
|
|
b1.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
|
|
util.SaveBlock(t, ctx, beaconDB, b1)
|
|
b2 := util.NewBeaconBlock()
|
|
b2.Block.Slot = 30
|
|
b2.Block.ParentRoot = bytesutil.PadTo([]byte{4}, 32)
|
|
util.SaveBlock(t, ctx, beaconDB, b2)
|
|
b3 := util.NewBeaconBlock()
|
|
b3.Block.Slot = 31
|
|
b3.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
|
|
util.SaveBlock(t, ctx, beaconDB, b3)
|
|
b4 := util.NewBeaconBlock()
|
|
b4.Block.Slot = 28
|
|
b4.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
|
|
util.SaveBlock(t, ctx, beaconDB, b4)
|
|
|
|
url := "http://example.com/eth/v1/beacon/headers"
|
|
|
|
t.Run("list headers", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
st, err := util.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, st.SetSlot(30))
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
State: st,
|
|
}
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
slot string
|
|
parentRoot string
|
|
want []*eth.SignedBeaconBlock
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "none",
|
|
want: []*eth.SignedBeaconBlock{
|
|
blkContainers[30].Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block,
|
|
b1,
|
|
b2,
|
|
},
|
|
},
|
|
{
|
|
name: "slot",
|
|
slot: "30",
|
|
want: []*eth.SignedBeaconBlock{
|
|
blkContainers[30].Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block,
|
|
b1,
|
|
b2,
|
|
},
|
|
},
|
|
{
|
|
name: "parent root",
|
|
parentRoot: hexutil.Encode(b1.Block.ParentRoot),
|
|
want: []*eth.SignedBeaconBlock{
|
|
blkContainers[1].Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block,
|
|
b1,
|
|
b3,
|
|
b4,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
urlWithParams := fmt.Sprintf("%s?slot=%s&parent_root=%s", url, tt.slot, tt.parentRoot)
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeadersResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
|
|
require.Equal(t, len(tt.want), len(resp.Data))
|
|
for i, blk := range tt.want {
|
|
expectedBodyRoot, err := blk.Block.Body.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
expectedHeader := ð.BeaconBlockHeader{
|
|
Slot: blk.Block.Slot,
|
|
ProposerIndex: blk.Block.ProposerIndex,
|
|
ParentRoot: blk.Block.ParentRoot,
|
|
StateRoot: make([]byte, 32),
|
|
BodyRoot: expectedBodyRoot[:],
|
|
}
|
|
expectedHeaderRoot, err := expectedHeader.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, hexutil.Encode(expectedHeaderRoot[:]), resp.Data[i].Root)
|
|
assert.DeepEqual(t, structs.BeaconBlockHeaderFromConsensus(expectedHeader), resp.Data[i].Header.Message)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
OptimisticRoots: map[[32]byte]bool{
|
|
bytesutil.ToBytes32(blkContainers[30].BlockRoot): true,
|
|
},
|
|
}
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
slot := primitives.Slot(30)
|
|
urlWithParams := fmt.Sprintf("%s?slot=%d", url, slot)
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeadersResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized", func(t *testing.T) {
|
|
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
|
require.NoError(t, err)
|
|
child1 := util.NewBeaconBlock()
|
|
child1.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32)
|
|
child1.Block.Slot = 999
|
|
util.SaveBlock(t, ctx, beaconDB, child1)
|
|
child2 := util.NewBeaconBlock()
|
|
child2.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32)
|
|
child2.Block.Slot = 1000
|
|
util.SaveBlock(t, ctx, beaconDB, child2)
|
|
child1Root, err := child1.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
child2Root, err := child2.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
mockChainFetcher := &chainMock.ChainService{
|
|
DB: beaconDB,
|
|
Block: wsb,
|
|
Root: headBlock.BlockRoot,
|
|
FinalizedCheckPoint: ð.Checkpoint{Root: blkContainers[64].BlockRoot},
|
|
FinalizedRoots: map[[32]byte]bool{child1Root: true, child2Root: false},
|
|
}
|
|
bs := &Server{
|
|
BeaconDB: beaconDB,
|
|
ChainInfoFetcher: mockChainFetcher,
|
|
OptimisticModeFetcher: mockChainFetcher,
|
|
FinalizationFetcher: mockChainFetcher,
|
|
}
|
|
|
|
t.Run("true", func(t *testing.T) {
|
|
slot := primitives.Slot(999)
|
|
urlWithParams := fmt.Sprintf("%s?slot=%d", url, slot)
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeadersResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.Finalized)
|
|
})
|
|
t.Run("false", func(t *testing.T) {
|
|
slot := primitives.Slot(1000)
|
|
urlWithParams := fmt.Sprintf("%s?slot=%d", url, slot)
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeadersResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, false, resp.Finalized)
|
|
})
|
|
t.Run("false when at least one not finalized", func(t *testing.T) {
|
|
urlWithParams := fmt.Sprintf("%s?parent_root=%s", url, hexutil.Encode(child1.Block.ParentRoot))
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeadersResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, false, resp.Finalized)
|
|
})
|
|
t.Run("no blocks found", func(t *testing.T) {
|
|
urlWithParams := fmt.Sprintf("%s?parent_root=%s", url, hexutil.Encode(bytes.Repeat([]byte{1}, 32)))
|
|
request := httptest.NewRequest(http.MethodGet, urlWithParams, nil)
|
|
writer := httptest.NewRecorder()
|
|
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
bs.GetBlockHeaders(writer, request)
|
|
require.Equal(t, http.StatusNotFound, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusNotFound, e.Code)
|
|
assert.StringContains(t, "No blocks found", e.Message)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestServer_GetBlockHeader(t *testing.T) {
|
|
b := util.NewBeaconBlock()
|
|
b.Block.Slot = 123
|
|
b.Block.ProposerIndex = 123
|
|
b.Block.StateRoot = bytesutil.PadTo([]byte("stateroot"), 32)
|
|
b.Block.ParentRoot = bytesutil.PadTo([]byte("parentroot"), 32)
|
|
b.Block.Body.Graffiti = bytesutil.PadTo([]byte("graffiti"), 32)
|
|
sb, err := blocks.NewSignedBeaconBlock(b)
|
|
sb.SetSignature(bytesutil.PadTo([]byte("sig"), 96))
|
|
require.NoError(t, err)
|
|
|
|
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
|
mockChainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
s := &Server{
|
|
ChainInfoFetcher: mockChainService,
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/headers/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockHeader(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeaderResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.Data.Canonical)
|
|
assert.Equal(t, "0xd7d92f6206707f2c9c4e7e82320617d5abac2b6461a65ea5bb1a154b5b5ea2fa", resp.Data.Root)
|
|
assert.Equal(t, "0x736967000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", resp.Data.Header.Signature)
|
|
assert.Equal(t, "123", resp.Data.Header.Message.Slot)
|
|
assert.Equal(t, "0x706172656e74726f6f7400000000000000000000000000000000000000000000", resp.Data.Header.Message.ParentRoot)
|
|
assert.Equal(t, "123", resp.Data.Header.Message.ProposerIndex)
|
|
assert.Equal(t, "0xdd32cbaa01c6c0ef399b293f86884ce6a15b532d34682edb16a48fa70ea5bc79", resp.Data.Header.Message.BodyRoot)
|
|
assert.Equal(t, "0x7374617465726f6f740000000000000000000000000000000000000000000000", resp.Data.Header.Message.StateRoot)
|
|
})
|
|
t.Run("missing block_id", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/headers/{block_id}", nil)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockHeader(writer, request)
|
|
require.Equal(t, http.StatusBadRequest, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusBadRequest, e.Code)
|
|
assert.StringContains(t, "block_id is required in URL params", e.Message)
|
|
})
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
r, err := sb.Block().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
mockChainService := &chainMock.ChainService{
|
|
OptimisticRoots: map[[32]byte]bool{r: true},
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
s := &Server{
|
|
ChainInfoFetcher: mockChainService,
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/headers/{block_id}", nil)
|
|
request.SetPathValue("block_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockHeader(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeaderResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
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{
|
|
ChainInfoFetcher: mockChainService,
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/headers/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(r[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockHeader(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeaderResponse{}
|
|
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{
|
|
ChainInfoFetcher: mockChainService,
|
|
OptimisticModeFetcher: mockChainService,
|
|
FinalizationFetcher: mockChainService,
|
|
Blocker: mockBlockFetcher,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/headers/{block_id}", nil)
|
|
request.SetPathValue("block_id", hexutil.Encode(r[:]))
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetBlockHeader(writer, request)
|
|
require.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetBlockHeaderResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, false, resp.Finalized)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetFinalityCheckpoints(t *testing.T) {
|
|
fillCheckpoints := func(state *eth.BeaconState) error {
|
|
state.PreviousJustifiedCheckpoint = ð.Checkpoint{
|
|
Root: bytesutil.PadTo([]byte("previous"), 32),
|
|
Epoch: 113,
|
|
}
|
|
state.CurrentJustifiedCheckpoint = ð.Checkpoint{
|
|
Root: bytesutil.PadTo([]byte("current"), 32),
|
|
Epoch: 123,
|
|
}
|
|
state.FinalizedCheckpoint = ð.Checkpoint{
|
|
Root: bytesutil.PadTo([]byte("finalized"), 32),
|
|
Epoch: 103,
|
|
}
|
|
return nil
|
|
}
|
|
fakeState, err := util.NewBeaconState(fillCheckpoints)
|
|
require.NoError(t, err)
|
|
|
|
stateProvider := func(ctx context.Context, stateId []byte) (state.BeaconState, error) {
|
|
if bytes.Equal(stateId, []byte("foobar")) {
|
|
return nil, &lookup.StateNotFoundError{}
|
|
}
|
|
return fakeState, nil
|
|
}
|
|
|
|
chainService := &chainMock.ChainService{}
|
|
s := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
StateProviderFunc: stateProvider,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetFinalityCheckpoints(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetFinalityCheckpointsResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.NotNil(t, resp.Data)
|
|
assert.Equal(t, strconv.FormatUint(uint64(fakeState.FinalizedCheckpoint().Epoch), 10), resp.Data.Finalized.Epoch)
|
|
assert.DeepEqual(t, hexutil.Encode(fakeState.FinalizedCheckpoint().Root), resp.Data.Finalized.Root)
|
|
assert.Equal(t, strconv.FormatUint(uint64(fakeState.CurrentJustifiedCheckpoint().Epoch), 10), resp.Data.CurrentJustified.Epoch)
|
|
assert.DeepEqual(t, hexutil.Encode(fakeState.CurrentJustifiedCheckpoint().Root), resp.Data.CurrentJustified.Root)
|
|
assert.Equal(t, strconv.FormatUint(uint64(fakeState.PreviousJustifiedCheckpoint().Epoch), 10), resp.Data.PreviousJustified.Epoch)
|
|
assert.DeepEqual(t, hexutil.Encode(fakeState.PreviousJustifiedCheckpoint().Root), resp.Data.PreviousJustified.Root)
|
|
})
|
|
t.Run("no state_id", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetFinalityCheckpoints(writer, request)
|
|
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusBadRequest, e.Code)
|
|
assert.StringContains(t, "state_id is required in URL params", e.Message)
|
|
})
|
|
t.Run("state not found", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
|
request.SetPathValue("state_id", "foobar")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetFinalityCheckpoints(writer, request)
|
|
assert.Equal(t, http.StatusNotFound, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusNotFound, e.Code)
|
|
assert.StringContains(t, "State not found", e.Message)
|
|
})
|
|
t.Run("execution optimistic", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{Optimistic: true}
|
|
s := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetFinalityCheckpoints(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetFinalityCheckpointsResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
t.Run("finalized", func(t *testing.T) {
|
|
headerRoot, err := fakeState.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
chainService := &chainMock.ChainService{
|
|
FinalizedRoots: map[[32]byte]bool{
|
|
headerRoot: true,
|
|
},
|
|
}
|
|
s := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: fakeState,
|
|
},
|
|
HeadFetcher: chainService,
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
|
request.SetPathValue("state_id", "head")
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetFinalityCheckpoints(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetFinalityCheckpointsResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
assert.Equal(t, true, resp.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetGenesis(t *testing.T) {
|
|
params.SetupTestConfigCleanup(t)
|
|
config := params.BeaconConfig().Copy()
|
|
config.GenesisForkVersion = []byte("genesis")
|
|
params.OverrideBeaconConfig(config)
|
|
genesis := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
validatorsRoot := [32]byte{1, 2, 3, 4, 5, 6}
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{
|
|
Genesis: genesis,
|
|
ValidatorsRoot: validatorsRoot,
|
|
}
|
|
s := Server{
|
|
GenesisTimeFetcher: chainService,
|
|
ChainInfoFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/genesis", nil)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetGenesis(writer, request)
|
|
assert.Equal(t, http.StatusOK, writer.Code)
|
|
resp := &structs.GetGenesisResponse{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
|
require.NotNil(t, resp.Data)
|
|
|
|
assert.Equal(t, strconv.FormatInt(genesis.Unix(), 10), resp.Data.GenesisTime)
|
|
assert.DeepEqual(t, hexutil.Encode(validatorsRoot[:]), resp.Data.GenesisValidatorsRoot)
|
|
assert.DeepEqual(t, hexutil.Encode([]byte("genesis")), resp.Data.GenesisForkVersion)
|
|
})
|
|
t.Run("no genesis time", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{
|
|
Genesis: time.Time{},
|
|
ValidatorsRoot: validatorsRoot,
|
|
}
|
|
s := Server{
|
|
GenesisTimeFetcher: chainService,
|
|
ChainInfoFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/genesis", nil)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetGenesis(writer, request)
|
|
assert.Equal(t, http.StatusNotFound, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusNotFound, e.Code)
|
|
assert.StringContains(t, "Chain genesis info is not yet known", e.Message)
|
|
})
|
|
t.Run("no genesis validators root", func(t *testing.T) {
|
|
chainService := &chainMock.ChainService{
|
|
Genesis: genesis,
|
|
ValidatorsRoot: [32]byte{},
|
|
}
|
|
s := Server{
|
|
GenesisTimeFetcher: chainService,
|
|
ChainInfoFetcher: chainService,
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/genesis", nil)
|
|
writer := httptest.NewRecorder()
|
|
writer.Body = &bytes.Buffer{}
|
|
|
|
s.GetGenesis(writer, request)
|
|
assert.Equal(t, http.StatusNotFound, writer.Code)
|
|
e := &httputil.DefaultJsonError{}
|
|
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
|
assert.Equal(t, http.StatusNotFound, e.Code)
|
|
assert.StringContains(t, "Chain genesis info is not yet known", e.Message)
|
|
})
|
|
}
|
|
|
|
func TestServer_broadcastBlobSidecars(t *testing.T) {
|
|
hook := logTest.NewGlobal()
|
|
blockToPropose := util.NewBeaconBlockContentsDeneb()
|
|
blockToPropose.Blobs = [][]byte{{0x01}, {0x02}, {0x03}}
|
|
blockToPropose.KzgProofs = [][]byte{{0x01}, {0x02}, {0x03}}
|
|
blockToPropose.Block.Block.Body.BlobKzgCommitments = [][]byte{bytesutil.PadTo([]byte("kc"), 48), bytesutil.PadTo([]byte("kc1"), 48), bytesutil.PadTo([]byte("kc2"), 48)}
|
|
d := ð.GenericSignedBeaconBlock_Deneb{Deneb: blockToPropose}
|
|
b := ð.GenericSignedBeaconBlock{Block: d}
|
|
|
|
server := &Server{
|
|
Broadcaster: &mockp2p.MockBroadcaster{},
|
|
FinalizationFetcher: &chainMock.ChainService{NotFinalized: true},
|
|
}
|
|
|
|
blk, err := blocks.NewSignedBeaconBlock(b.Block)
|
|
require.NoError(t, err)
|
|
require.NoError(t, server.broadcastSeenBlockSidecars(t.Context(), blk, b.GetDeneb().Blobs, b.GetDeneb().KzgProofs))
|
|
require.LogsDoNotContain(t, hook, "Broadcasted blob sidecar for already seen block")
|
|
|
|
server.FinalizationFetcher = &chainMock.ChainService{NotFinalized: false}
|
|
require.NoError(t, server.broadcastSeenBlockSidecars(t.Context(), blk, b.GetDeneb().Blobs, b.GetDeneb().KzgProofs))
|
|
require.LogsContain(t, hook, "Broadcasted blob sidecar for already seen block")
|
|
}
|
|
|
|
func Test_validateBlobs(t *testing.T) {
|
|
params.SetupTestConfigCleanup(t)
|
|
params.BeaconConfig().FuluForkEpoch = params.BeaconConfig().ElectraForkEpoch + 4096*2
|
|
ds := util.SlotAtEpoch(t, params.BeaconConfig().DenebForkEpoch)
|
|
es := util.SlotAtEpoch(t, params.BeaconConfig().ElectraForkEpoch)
|
|
fe := params.BeaconConfig().FuluForkEpoch
|
|
fs := util.SlotAtEpoch(t, fe)
|
|
|
|
require.NoError(t, kzg.Start())
|
|
|
|
denebMax := params.BeaconConfig().MaxBlobsPerBlock(ds)
|
|
blob := util.GetRandBlob(123)
|
|
// Generate proper commitment and proof for the blob
|
|
var kzgBlob kzg.Blob
|
|
copy(kzgBlob[:], blob[:])
|
|
commitment, err := kzg.BlobToKZGCommitment(&kzgBlob)
|
|
require.NoError(t, err)
|
|
proof, err := kzg.ComputeBlobKZGProof(&kzgBlob, commitment)
|
|
require.NoError(t, err)
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = [][]byte{commitment[:]}
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
require.NoError(t, s.validateBlobs(b, [][]byte{blob[:]}, [][]byte{proof[:]}))
|
|
|
|
require.ErrorContains(t, "number of blobs (1), proofs (0), and commitments (1) do not match", s.validateBlobs(b, [][]byte{blob[:]}, [][]byte{}))
|
|
|
|
sk, err := bls.RandKey()
|
|
require.NoError(t, err)
|
|
blk.Block.Body.BlobKzgCommitments = [][]byte{sk.PublicKey().Marshal()}
|
|
b, err = blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
require.ErrorContains(t, "could not verify blob proofs", s.validateBlobs(b, [][]byte{blob[:]}, [][]byte{proof[:]}))
|
|
|
|
electraMax := params.BeaconConfig().MaxBlobsPerBlock(es)
|
|
blobs := [][]byte{}
|
|
commitments := [][]byte{}
|
|
proofs := [][]byte{}
|
|
for i := 0; i < electraMax+1; i++ {
|
|
blobs = append(blobs, blob[:])
|
|
commitments = append(commitments, commitment[:])
|
|
proofs = append(proofs, proof[:])
|
|
}
|
|
t.Run("pre-Deneb block should return early", func(t *testing.T) {
|
|
// Create a pre-Deneb block (e.g., Capella)
|
|
blk := util.NewBeaconBlockCapella()
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
// Should return nil for pre-Deneb blocks regardless of blobs
|
|
require.NoError(t, s.validateBlobs(b, [][]byte{}, [][]byte{}))
|
|
require.NoError(t, s.validateBlobs(b, blobs[:1], proofs[:1]))
|
|
})
|
|
|
|
t.Run("Deneb block with valid single blob", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = [][]byte{commitment[:]}
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
require.NoError(t, s.validateBlobs(b, [][]byte{blob[:]}, [][]byte{proof[:]}))
|
|
})
|
|
|
|
t.Run("Deneb block with max blobs (6)", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:6]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
// Should pass with exactly 6 blobs
|
|
require.NoError(t, s.validateBlobs(b, blobs[:denebMax], proofs[:denebMax]))
|
|
})
|
|
|
|
t.Run("Deneb block exceeding max blobs", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:7]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
// Should fail with 7 blobs when max is 6
|
|
err = s.validateBlobs(b, blobs[:denebMax+1], proofs[:denebMax+1])
|
|
require.ErrorContains(t, "number of blobs over max", err)
|
|
})
|
|
|
|
t.Run("Electra block with valid blobs", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockElectra()
|
|
blk.Block.Slot = es
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:9]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
// Should pass with 9 blobs in Electra
|
|
require.NoError(t, s.validateBlobs(b, blobs[:electraMax], proofs[:electraMax]))
|
|
})
|
|
|
|
t.Run("Electra block exceeding max blobs", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockElectra()
|
|
blk.Block.Slot = es
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:electraMax+1]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
s := &Server{}
|
|
// Should fail with 10 blobs when max is 9
|
|
err = s.validateBlobs(b, blobs[:electraMax+1], proofs[:electraMax+1])
|
|
require.ErrorContains(t, "number of blobs over max", err)
|
|
})
|
|
|
|
t.Run("Fulu block with valid cell proofs", func(t *testing.T) {
|
|
const numberOfColumns = fieldparams.NumberOfColumns
|
|
blk := util.NewBeaconBlockFulu()
|
|
blk.Block.Slot = fs
|
|
|
|
// Generate valid commitments and cell proofs for testing
|
|
blobCount := 2
|
|
commitments := make([][]byte, blobCount)
|
|
fuluBlobs := make([][]byte, blobCount)
|
|
var kzgBlobs []kzg.Blob
|
|
|
|
for i := range blobCount {
|
|
blob := util.GetRandBlob(int64(i))
|
|
fuluBlobs[i] = blob[:]
|
|
var kzgBlob kzg.Blob
|
|
copy(kzgBlob[:], blob[:])
|
|
kzgBlobs = append(kzgBlobs, kzgBlob)
|
|
|
|
// Generate commitment
|
|
commitment, err := kzg.BlobToKZGCommitment(&kzgBlob)
|
|
require.NoError(t, err)
|
|
commitments[i] = commitment[:]
|
|
}
|
|
|
|
blk.Block.Body.BlobKzgCommitments = commitments
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
// Generate cell proofs for the blobs (flattened format like execution client)
|
|
cellProofs := make([][]byte, uint64(blobCount)*numberOfColumns)
|
|
for blobIdx := range blobCount {
|
|
_, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlobs[blobIdx])
|
|
require.NoError(t, err)
|
|
|
|
for colIdx := range numberOfColumns {
|
|
cellProofIdx := blobIdx*numberOfColumns + colIdx
|
|
cellProofs[cellProofIdx] = proofs[colIdx][:]
|
|
}
|
|
}
|
|
|
|
s := &Server{}
|
|
// Should use cell batch verification for Fulu blocks
|
|
require.NoError(t, s.validateBlobs(b, fuluBlobs, cellProofs))
|
|
})
|
|
|
|
t.Run("Fulu block with invalid cell proof count", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockFulu()
|
|
blk.Block.Slot = fs
|
|
|
|
// Create valid commitments but wrong number of cell proofs
|
|
blobCount := 2
|
|
commitments := make([][]byte, blobCount)
|
|
fuluBlobs := make([][]byte, blobCount)
|
|
for i := range blobCount {
|
|
blob := util.GetRandBlob(int64(i))
|
|
fuluBlobs[i] = blob[:]
|
|
|
|
var kzgBlob kzg.Blob
|
|
copy(kzgBlob[:], blob[:])
|
|
commitment, err := kzg.BlobToKZGCommitment(&kzgBlob)
|
|
require.NoError(t, err)
|
|
commitments[i] = commitment[:]
|
|
}
|
|
|
|
blk.Block.Body.BlobKzgCommitments = commitments
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
// Wrong number of cell proofs (should be blobCount * numberOfColumns)
|
|
wrongCellProofs := make([][]byte, 10) // Too few proofs
|
|
|
|
s := &Server{}
|
|
err = s.validateBlobs(b, fuluBlobs, wrongCellProofs)
|
|
require.ErrorContains(t, "do not match", err)
|
|
})
|
|
|
|
t.Run("Deneb block with invalid blob proof", func(t *testing.T) {
|
|
blob := util.GetRandBlob(123)
|
|
invalidProof := make([]byte, 48) // All zeros - invalid proof
|
|
|
|
sk, err := bls.RandKey()
|
|
require.NoError(t, err)
|
|
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = [][]byte{sk.PublicKey().Marshal()}
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{}
|
|
err = s.validateBlobs(b, [][]byte{blob[:]}, [][]byte{invalidProof})
|
|
require.ErrorContains(t, "could not verify blob proofs", err)
|
|
})
|
|
|
|
t.Run("empty blobs and proofs should pass", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = [][]byte{}
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
s := &Server{}
|
|
require.NoError(t, s.validateBlobs(b, [][]byte{}, [][]byte{}))
|
|
})
|
|
|
|
t.Run("BlobSchedule with progressive increases (BPO)", func(t *testing.T) {
|
|
cfg := params.BeaconConfig().Copy()
|
|
defer params.OverrideBeaconConfig(cfg)
|
|
|
|
// Set up config with BlobSchedule (BPO - Blob Production Optimization)
|
|
testCfg := params.BeaconConfig().Copy()
|
|
testCfg.DeprecatedMaxBlobsPerBlock = 6
|
|
testCfg.DeprecatedMaxBlobsPerBlockElectra = 9
|
|
// Define blob schedule with progressive increases
|
|
testCfg.BlobSchedule = []params.BlobScheduleEntry{
|
|
{Epoch: fe + 1, MaxBlobsPerBlock: 3}, // Start with 3 blobs
|
|
{Epoch: fe + 10, MaxBlobsPerBlock: 5}, // Increase to 5 at epoch 10
|
|
{Epoch: fe + 20, MaxBlobsPerBlock: 7}, // Increase to 7 at epoch 20
|
|
{Epoch: fe + 30, MaxBlobsPerBlock: 9}, // Increase to 9 at epoch 30
|
|
}
|
|
params.OverrideBeaconConfig(testCfg)
|
|
|
|
s := &Server{}
|
|
t.Run("deneb under and over max", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockDeneb()
|
|
blk.Block.Slot = ds
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:denebMax]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.validateBlobs(b, blobs[:denebMax], proofs[:denebMax]))
|
|
|
|
// Should fail with 4 blobs
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:4]
|
|
b, err = blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
err = s.validateBlobs(b, blobs[:denebMax+1], proofs[:denebMax+1])
|
|
require.ErrorContains(t, "number of blobs over max", err)
|
|
})
|
|
|
|
// Test epoch 30+: max 9 blobs
|
|
t.Run("different max in electra", func(t *testing.T) {
|
|
blk := util.NewBeaconBlockElectra()
|
|
blk.Block.Slot = es
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:electraMax]
|
|
b, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.validateBlobs(b, blobs[:electraMax], proofs[:electraMax]))
|
|
|
|
// exceed the electra max
|
|
blk.Block.Body.BlobKzgCommitments = commitments[:electraMax+1]
|
|
b, err = blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
err = s.validateBlobs(b, blobs[:electraMax+1], proofs[:electraMax+1])
|
|
require.ErrorContains(t, "number of blobs over max, 10 > 9", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetPendingConsolidations(t *testing.T) {
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 10)
|
|
|
|
cs := make([]*eth.PendingConsolidation, 10)
|
|
for i := 0; i < len(cs); i += 1 {
|
|
cs[i] = ð.PendingConsolidation{
|
|
SourceIndex: primitives.ValidatorIndex(i),
|
|
TargetIndex: primitives.ValidatorIndex(i + 1),
|
|
}
|
|
}
|
|
require.NoError(t, st.SetPendingConsolidations(cs))
|
|
|
|
chainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
server := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
t.Run("json response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
var resp structs.GetPendingConsolidationsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
expectedVersion := version.String(st.Version())
|
|
require.Equal(t, expectedVersion, resp.Version)
|
|
|
|
require.Equal(t, false, resp.ExecutionOptimistic)
|
|
require.Equal(t, false, resp.Finalized)
|
|
|
|
expectedConsolidations := structs.PendingConsolidationsFromConsensus(cs)
|
|
require.DeepEqual(t, expectedConsolidations, resp.Data)
|
|
})
|
|
t.Run("ssz response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
req.Header.Set("Accept", "application/octet-stream")
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
responseBytes := rec.Body.Bytes()
|
|
var recoveredConsolidations []*eth.PendingConsolidation
|
|
|
|
// Verify total size matches expected number of deposits
|
|
consolidationSize := (ð.PendingConsolidation{}).SizeSSZ()
|
|
require.Equal(t, len(responseBytes), consolidationSize*len(cs))
|
|
|
|
for i := range cs {
|
|
start := i * consolidationSize
|
|
end := start + consolidationSize
|
|
|
|
var c eth.PendingConsolidation
|
|
require.NoError(t, c.UnmarshalSSZ(responseBytes[start:end]))
|
|
recoveredConsolidations = append(recoveredConsolidations, &c)
|
|
}
|
|
require.DeepEqual(t, cs, recoveredConsolidations)
|
|
})
|
|
t.Run("pre electra state", func(t *testing.T) {
|
|
preElectraSt, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
|
preElectraServer := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: preElectraSt,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
// Test JSON request
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is prior to electra", errResp.Message)
|
|
|
|
// Test SSZ request
|
|
sszReq := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
sszReq.Header.Set("Accept", "application/octet-stream")
|
|
sszReq.SetPathValue("state_id", "head")
|
|
sszRec := httptest.NewRecorder()
|
|
sszRec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingConsolidations(sszRec, sszReq)
|
|
require.Equal(t, http.StatusBadRequest, sszRec.Code)
|
|
|
|
var sszErrResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(sszRec.Body.Bytes(), &sszErrResp))
|
|
require.Equal(t, "state_id is prior to electra", sszErrResp.Message)
|
|
})
|
|
t.Run("missing state_id parameter", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
// Intentionally not setting state_id
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is required in URL params", errResp.Message)
|
|
})
|
|
t.Run("optimistic node", func(t *testing.T) {
|
|
optimisticChainService := &chainMock.ChainService{
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
optimisticServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: optimisticChainService,
|
|
FinalizationFetcher: optimisticChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
optimisticServer.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingConsolidationsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized node", func(t *testing.T) {
|
|
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
finalizedChainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{blockRoot: true},
|
|
}
|
|
finalizedServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: finalizedChainService,
|
|
FinalizationFetcher: finalizedChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_consolidations", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
finalizedServer.GetPendingConsolidations(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingConsolidationsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetPendingDeposits(t *testing.T) {
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 10)
|
|
|
|
validators := st.Validators()
|
|
dummySig := make([]byte, 96)
|
|
for j := range 96 {
|
|
dummySig[j] = byte(j)
|
|
}
|
|
deps := make([]*eth.PendingDeposit, 10)
|
|
for i := 0; i < len(deps); i += 1 {
|
|
deps[i] = ð.PendingDeposit{
|
|
PublicKey: validators[i].PublicKey,
|
|
WithdrawalCredentials: validators[i].WithdrawalCredentials,
|
|
Amount: 100,
|
|
Slot: 0,
|
|
Signature: dummySig,
|
|
}
|
|
}
|
|
require.NoError(t, st.SetPendingDeposits(deps))
|
|
|
|
chainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
server := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
t.Run("json response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
var resp structs.GetPendingDepositsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
expectedVersion := version.String(st.Version())
|
|
require.Equal(t, expectedVersion, resp.Version)
|
|
|
|
require.Equal(t, false, resp.ExecutionOptimistic)
|
|
require.Equal(t, false, resp.Finalized)
|
|
|
|
expectedDeposits := structs.PendingDepositsFromConsensus(deps)
|
|
require.DeepEqual(t, expectedDeposits, resp.Data)
|
|
})
|
|
t.Run("ssz response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
req.Header.Set("Accept", "application/octet-stream")
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
responseBytes := rec.Body.Bytes()
|
|
var recoveredDeposits []*eth.PendingDeposit
|
|
|
|
// Verify total size matches expected number of deposits
|
|
depositSize := (ð.PendingDeposit{}).SizeSSZ()
|
|
require.Equal(t, len(responseBytes), depositSize*len(deps))
|
|
|
|
for i := range deps {
|
|
start := i * depositSize
|
|
end := start + depositSize
|
|
|
|
var deposit eth.PendingDeposit
|
|
require.NoError(t, deposit.UnmarshalSSZ(responseBytes[start:end]))
|
|
recoveredDeposits = append(recoveredDeposits, &deposit)
|
|
}
|
|
require.DeepEqual(t, deps, recoveredDeposits)
|
|
})
|
|
t.Run("pre electra state", func(t *testing.T) {
|
|
preElectraSt, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
|
preElectraServer := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: preElectraSt,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
// Test JSON request
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is prior to electra", errResp.Message)
|
|
|
|
// Test SSZ request
|
|
sszReq := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
sszReq.Header.Set("Accept", "application/octet-stream")
|
|
sszReq.SetPathValue("state_id", "head")
|
|
sszRec := httptest.NewRecorder()
|
|
sszRec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingDeposits(sszRec, sszReq)
|
|
require.Equal(t, http.StatusBadRequest, sszRec.Code)
|
|
|
|
var sszErrResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(sszRec.Body.Bytes(), &sszErrResp))
|
|
require.Equal(t, "state_id is prior to electra", sszErrResp.Message)
|
|
})
|
|
t.Run("missing state_id parameter", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
// Intentionally not setting state_id
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is required in URL params", errResp.Message)
|
|
})
|
|
t.Run("optimistic node", func(t *testing.T) {
|
|
optimisticChainService := &chainMock.ChainService{
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
optimisticServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: optimisticChainService,
|
|
FinalizationFetcher: optimisticChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
optimisticServer.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingDepositsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized node", func(t *testing.T) {
|
|
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
finalizedChainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{blockRoot: true},
|
|
}
|
|
finalizedServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: finalizedChainService,
|
|
FinalizationFetcher: finalizedChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_deposits", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
finalizedServer.GetPendingDeposits(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingDepositsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetPendingPartialWithdrawals(t *testing.T) {
|
|
st, _ := util.DeterministicGenesisStateElectra(t, 10)
|
|
for i := 0; i < 10; i += 1 {
|
|
err := st.AppendPendingPartialWithdrawal(
|
|
ð.PendingPartialWithdrawal{
|
|
Index: primitives.ValidatorIndex(i),
|
|
Amount: 100,
|
|
WithdrawableEpoch: primitives.Epoch(0),
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
withdrawals, err := st.PendingPartialWithdrawals()
|
|
require.NoError(t, err)
|
|
|
|
chainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
server := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
t.Run("json response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
var resp structs.GetPendingPartialWithdrawalsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
expectedVersion := version.String(st.Version())
|
|
require.Equal(t, expectedVersion, resp.Version)
|
|
|
|
require.Equal(t, false, resp.ExecutionOptimistic)
|
|
require.Equal(t, false, resp.Finalized)
|
|
|
|
expectedWithdrawals := structs.PendingPartialWithdrawalsFromConsensus(withdrawals)
|
|
require.DeepEqual(t, expectedWithdrawals, resp.Data)
|
|
})
|
|
|
|
t.Run("ssz response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
req.Header.Set("Accept", "application/octet-stream")
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "electra", rec.Header().Get(api.VersionHeader))
|
|
|
|
responseBytes := rec.Body.Bytes()
|
|
var recoveredWithdrawals []*eth.PendingPartialWithdrawal
|
|
|
|
withdrawalSize := (ð.PendingPartialWithdrawal{}).SizeSSZ()
|
|
require.Equal(t, len(responseBytes), withdrawalSize*len(withdrawals))
|
|
|
|
for i := range withdrawals {
|
|
start := i * withdrawalSize
|
|
end := start + withdrawalSize
|
|
|
|
var withdrawal eth.PendingPartialWithdrawal
|
|
require.NoError(t, withdrawal.UnmarshalSSZ(responseBytes[start:end]))
|
|
recoveredWithdrawals = append(recoveredWithdrawals, &withdrawal)
|
|
}
|
|
require.DeepEqual(t, withdrawals, recoveredWithdrawals)
|
|
})
|
|
|
|
t.Run("pre electra state", func(t *testing.T) {
|
|
preElectraSt, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
|
preElectraServer := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: preElectraSt,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
// Test JSON request
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is prior to electra", errResp.Message)
|
|
|
|
// Test SSZ request
|
|
sszReq := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
sszReq.Header.Set("Accept", "application/octet-stream")
|
|
sszReq.SetPathValue("state_id", "head")
|
|
sszRec := httptest.NewRecorder()
|
|
sszRec.Body = new(bytes.Buffer)
|
|
|
|
preElectraServer.GetPendingPartialWithdrawals(sszRec, sszReq)
|
|
require.Equal(t, http.StatusBadRequest, sszRec.Code)
|
|
|
|
var sszErrResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(sszRec.Body.Bytes(), &sszErrResp))
|
|
require.Equal(t, "state_id is prior to electra", sszErrResp.Message)
|
|
})
|
|
|
|
t.Run("missing state_id parameter", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
// Intentionally not setting state_id
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is required in URL params", errResp.Message)
|
|
})
|
|
|
|
t.Run("optimistic node", func(t *testing.T) {
|
|
optimisticChainService := &chainMock.ChainService{
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
optimisticServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: optimisticChainService,
|
|
FinalizationFetcher: optimisticChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
optimisticServer.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingPartialWithdrawalsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized node", func(t *testing.T) {
|
|
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
finalizedChainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{blockRoot: true},
|
|
}
|
|
finalizedServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: finalizedChainService,
|
|
FinalizationFetcher: finalizedChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
finalizedServer.GetPendingPartialWithdrawals(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetPendingPartialWithdrawalsResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.Finalized)
|
|
})
|
|
}
|
|
|
|
func TestGetProposerLookahead(t *testing.T) {
|
|
numValidators := 50
|
|
// Create a Fulu state with proposer lookahead data
|
|
st, _ := util.DeterministicGenesisStateFulu(t, uint64(numValidators))
|
|
lookaheadSize := int(params.BeaconConfig().MinSeedLookahead+1) * int(params.BeaconConfig().SlotsPerEpoch)
|
|
lookahead := make([]primitives.ValidatorIndex, lookaheadSize)
|
|
for i := range lookaheadSize {
|
|
lookahead[i] = primitives.ValidatorIndex(i % numValidators) // Cycle through validators
|
|
}
|
|
|
|
require.NoError(t, st.SetProposerLookahead(lookahead))
|
|
|
|
chainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
server := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: st,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
t.Run("json response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "fulu", rec.Header().Get(api.VersionHeader))
|
|
|
|
var resp structs.GetProposerLookaheadResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
expectedVersion := version.String(st.Version())
|
|
require.Equal(t, expectedVersion, resp.Version)
|
|
require.Equal(t, false, resp.ExecutionOptimistic)
|
|
require.Equal(t, false, resp.Finalized)
|
|
|
|
// Verify the data
|
|
require.Equal(t, lookaheadSize, len(resp.Data))
|
|
for i := range lookaheadSize {
|
|
expectedIdx := strconv.FormatUint(uint64(i%numValidators), 10)
|
|
require.Equal(t, expectedIdx, resp.Data[i])
|
|
}
|
|
})
|
|
|
|
t.Run("ssz response", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
req.Header.Set("Accept", "application/octet-stream")
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, "fulu", rec.Header().Get(api.VersionHeader))
|
|
responseBytes := rec.Body.Bytes()
|
|
validatorIndexSize := (*primitives.ValidatorIndex)(nil).SizeSSZ()
|
|
require.Equal(t, len(responseBytes), validatorIndexSize*lookaheadSize)
|
|
|
|
recoveredIndices := make([]primitives.ValidatorIndex, lookaheadSize)
|
|
for i := range lookaheadSize {
|
|
start := i * validatorIndexSize
|
|
end := start + validatorIndexSize
|
|
|
|
idx := ssz.UnmarshallUint64(responseBytes[start:end])
|
|
recoveredIndices[i] = primitives.ValidatorIndex(idx)
|
|
}
|
|
require.DeepEqual(t, lookahead, recoveredIndices)
|
|
})
|
|
|
|
t.Run("pre fulu state", func(t *testing.T) {
|
|
preEplusSt, _ := util.DeterministicGenesisStateElectra(t, 1)
|
|
preFuluServer := &Server{
|
|
Stater: &testutil.MockStater{
|
|
BeaconState: preEplusSt,
|
|
},
|
|
OptimisticModeFetcher: chainService,
|
|
FinalizationFetcher: chainService,
|
|
}
|
|
|
|
// Test JSON request
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
preFuluServer.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is prior to fulu", errResp.Message)
|
|
|
|
// Test SSZ request
|
|
sszReq := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
sszReq.Header.Set("Accept", "application/octet-stream")
|
|
sszReq.SetPathValue("state_id", "head")
|
|
sszRec := httptest.NewRecorder()
|
|
sszRec.Body = new(bytes.Buffer)
|
|
|
|
preFuluServer.GetProposerLookahead(sszRec, sszReq)
|
|
require.Equal(t, http.StatusBadRequest, sszRec.Code)
|
|
|
|
var sszErrResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(sszRec.Body.Bytes(), &sszErrResp))
|
|
require.Equal(t, "state_id is prior to fulu", sszErrResp.Message)
|
|
})
|
|
|
|
t.Run("missing state_id parameter", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
server.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
|
|
|
var errResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp))
|
|
require.Equal(t, "state_id is required in URL params", errResp.Message)
|
|
})
|
|
|
|
t.Run("optimistic node", func(t *testing.T) {
|
|
optimisticChainService := &chainMock.ChainService{
|
|
Optimistic: true,
|
|
FinalizedRoots: map[[32]byte]bool{},
|
|
}
|
|
optimisticServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: optimisticChainService,
|
|
FinalizationFetcher: optimisticChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
optimisticServer.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetProposerLookaheadResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.ExecutionOptimistic)
|
|
})
|
|
|
|
t.Run("finalized node", func(t *testing.T) {
|
|
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
|
require.NoError(t, err)
|
|
|
|
finalizedChainService := &chainMock.ChainService{
|
|
Optimistic: false,
|
|
FinalizedRoots: map[[32]byte]bool{blockRoot: true},
|
|
}
|
|
finalizedServer := &Server{
|
|
Stater: server.Stater,
|
|
OptimisticModeFetcher: finalizedChainService,
|
|
FinalizationFetcher: finalizedChainService,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil)
|
|
req.SetPathValue("state_id", "head")
|
|
rec := httptest.NewRecorder()
|
|
rec.Body = new(bytes.Buffer)
|
|
|
|
finalizedServer.GetProposerLookahead(rec, req)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp structs.GetProposerLookaheadResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.Equal(t, true, resp.Finalized)
|
|
})
|
|
}
|