mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
47 Commits
backfill-r
...
payloadIDl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b67b3aae0b | ||
|
|
7728a352a7 | ||
|
|
e76aedf1ae | ||
|
|
0e5d299d02 | ||
|
|
14f040de48 | ||
|
|
1a1a30591e | ||
|
|
3070878d59 | ||
|
|
f59307358e | ||
|
|
ef1f5e6dbe | ||
|
|
a22ca3fecb | ||
|
|
14ce051668 | ||
|
|
998a493ee2 | ||
|
|
398f44bb53 | ||
|
|
4098b3a1d2 | ||
|
|
d8e6d2cb2e | ||
|
|
6b915bab26 | ||
|
|
dd73f762ec | ||
|
|
4d120b53ae | ||
|
|
4d6b3252ae | ||
|
|
9bb81537c8 | ||
|
|
0fdf63b565 | ||
|
|
bd85b0e4e1 | ||
|
|
d1562bab53 | ||
|
|
1e29877406 | ||
|
|
8c39c55f05 | ||
|
|
0ccfc74e86 | ||
|
|
9ecf4d34f5 | ||
|
|
a8793c9f21 | ||
|
|
79445d2bf6 | ||
|
|
35fc1c976f | ||
|
|
b0423a94af | ||
|
|
6c16e90fe9 | ||
|
|
c0a01bb859 | ||
|
|
2a408a0dd8 | ||
|
|
af16c71d6e | ||
|
|
ec954ec9a6 | ||
|
|
7781a3186b | ||
|
|
440cf32966 | ||
|
|
13c69af717 | ||
|
|
ca88e59ee3 | ||
|
|
809a67ebcc | ||
|
|
f17898f658 | ||
|
|
d506f9b2da | ||
|
|
d55757500f | ||
|
|
41dff74e90 | ||
|
|
4f48c551da | ||
|
|
2f6dcb34b6 |
59
.github/ISSUE_TEMPLATE/bug_report.md
vendored
59
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,59 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41EBug report"
|
||||
about: Report a bug or problem with running Prysm
|
||||
---
|
||||
<!--💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎
|
||||
|
||||
Hellooo! 😄
|
||||
|
||||
To help us tend to your issue faster, please search our currently open issues before submitting a new one.
|
||||
Existing issues often contain information about workarounds, resolution, or progress updates.
|
||||
|
||||
💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎-->
|
||||
|
||||
# 🐞 Bug Report
|
||||
|
||||
### Description
|
||||
|
||||
<!-- ✍️--> A clear and concise description of the problem...
|
||||
|
||||
### Has this worked before in a previous version?
|
||||
|
||||
<!-- Did this behavior use to work in the previous version? -->
|
||||
<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
|
||||
|
||||
## 🔬 Minimal Reproduction
|
||||
|
||||
<!--
|
||||
Please let us know how we can reproduce this issue. Include the exact method you used to run Prysm along with any flags used in your beacon chain and/or validator. Make sure you don't upload any confidential files or private keys.
|
||||
-->
|
||||
|
||||
## 🔥 Error
|
||||
|
||||
<pre><code>
|
||||
<!-- If the issue is accompanied by an error, please share the error logs with us below. If you have a lot of logs, place make a paste bin with your logs and share the link with us here: -->
|
||||
<!-- ✍️-->
|
||||
|
||||
</code></pre>
|
||||
|
||||
|
||||
## 🌍 Your Environment
|
||||
|
||||
**Operating System:**
|
||||
|
||||
<pre>
|
||||
<code>
|
||||
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
**What version of Prysm are you running? (Which release)**
|
||||
|
||||
<pre>
|
||||
<code>
|
||||
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
**Anything else relevant (validator index / public key)?**
|
||||
|
||||
79
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
79
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or problem with running Prysm
|
||||
labels: ["Bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Hellooo! 😄
|
||||
To help us tend to your issue faster, please search our currently open issues before submitting a new one.
|
||||
Existing issues often contain information about workarounds, resolution, or progress updates.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: |
|
||||
A clear and concise description of the problem...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: previous-version
|
||||
attributes:
|
||||
label: Has this worked before in a previous version?
|
||||
description: Did this behavior use to work in the previous version?
|
||||
render: text
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: 🔬 Minimal Reproduction
|
||||
description: |
|
||||
Please let us know how we can reproduce this issue.
|
||||
Include the exact method you used to run Prysm along with any flags used in your beacon chain and/or validator.
|
||||
Make sure you don't upload any confidential files or private keys.
|
||||
placeholder: |
|
||||
Steps to reproduce:
|
||||
|
||||
1. Start '...'
|
||||
2. Then '...'
|
||||
3. Check '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: errors
|
||||
attributes:
|
||||
label: 🔥 Error
|
||||
description: |
|
||||
If the issue is accompanied by an error, please share the error logs with us below.
|
||||
If you have a lot of logs, place make a paste bin with your logs and share the link with us here:
|
||||
render: text
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 🌍 Platform(s)
|
||||
description: What platform(s) did this occur on?
|
||||
multiple: true
|
||||
options:
|
||||
- Linux (x86)
|
||||
- Linux (ARM)
|
||||
- Mac (Intel)
|
||||
- Mac (Apple Silicon)
|
||||
- Windows (x86)
|
||||
- Windows (ARM)
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Prysm are you running? (Which release)
|
||||
description: You can check your Prysm version by running your beacon node or validator with the `--version` flag.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else relevant (validator index / public key)?
|
||||
validations:
|
||||
required: false
|
||||
@@ -11,6 +11,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/api/client/builder",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
@@ -41,6 +42,7 @@ go_test(
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
@@ -54,5 +56,6 @@ go_test(
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_golang_protobuf//proto:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
@@ -365,8 +366,10 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not get protobuf block")
|
||||
}
|
||||
|
||||
b := ðpb.SignedBlindedBeaconBlockAndBlobsDeneb{Block: psb, Blobs: blobs}
|
||||
b, err := shared.SignedBlindedBeaconBlockContentsDenebFromConsensus(ðpb.SignedBlindedBeaconBlockAndBlobsDeneb{SignedBlindedBlock: psb, SignedBlindedBlobSidecars: blobs})
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not convert SignedBlindedBeaconBlockContentsDeneb to json marshalable type")
|
||||
}
|
||||
body, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockDeneb value body in SubmitBlindedBlockDeneb")
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type roundtrip func(*http.Request) (*http.Response, error)
|
||||
@@ -283,6 +285,42 @@ func TestClient_GetHeader(t *testing.T) {
|
||||
require.Equal(t, len(bundle.Proofs[i]) == 48, true)
|
||||
}
|
||||
})
|
||||
t.Run("deneb, no bundle", func(t *testing.T) {
|
||||
hc := &http.Client{
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, expectedPath, r.URL.Path)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(testExampleHeaderResponseDenebNoBundle)),
|
||||
Request: r.Clone(ctx),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
c := &Client{
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
h, err := c.GetHeader(ctx, slot, bytesutil.ToBytes32(parentHash), bytesutil.ToBytes48(pubkey))
|
||||
require.NoError(t, err)
|
||||
expectedWithdrawalsRoot := ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2")
|
||||
bid, err := h.Message()
|
||||
require.NoError(t, err)
|
||||
bidHeader, err := bid.Header()
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot, err := bidHeader.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, bytes.Equal(expectedWithdrawalsRoot, withdrawalsRoot))
|
||||
value, err := stringToUint256("652312848583266388373324160190187140051835877600158453279131187530910662656")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fmt.Sprintf("%#x", value.SSZBytes()), fmt.Sprintf("%#x", bid.Value()))
|
||||
bidValue := bytesutil.ReverseByteOrder(bid.Value())
|
||||
require.DeepEqual(t, bidValue, value.Bytes())
|
||||
require.DeepEqual(t, big.NewInt(0).SetBytes(bidValue), value.Int)
|
||||
bundle, err := bid.BlindedBlobsBundle()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, (*v1.BlindedBlobsBundle)(nil), bundle)
|
||||
})
|
||||
|
||||
t.Run("unsupported version", func(t *testing.T) {
|
||||
hc := &http.Client{
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
@@ -361,10 +399,18 @@ func TestSubmitBlindedBlock(t *testing.T) {
|
||||
assert.Equal(t, uint64(1), withdrawals[0].Amount)
|
||||
})
|
||||
t.Run("deneb", func(t *testing.T) {
|
||||
|
||||
test := testSignedBlindedBeaconBlockAndBlobsDeneb(t)
|
||||
hc := &http.Client{
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, postBlindedBeaconBlockPath, r.URL.Path)
|
||||
require.Equal(t, "deneb", r.Header.Get("Eth-Consensus-Version"))
|
||||
var req shared.SignedBlindedBeaconBlockContentsDeneb
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
require.NoError(t, err)
|
||||
block, err := req.SignedBlindedBlock.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, block, test.SignedBlindedBlock)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(testExampleExecutionPayloadDeneb)),
|
||||
@@ -376,11 +422,11 @@ func TestSubmitBlindedBlock(t *testing.T) {
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
test := testSignedBlindedBeaconBlockAndBlobsDeneb(t)
|
||||
sbb, err := blocks.NewSignedBeaconBlock(test.Block)
|
||||
|
||||
sbb, err := blocks.NewSignedBeaconBlock(test.SignedBlindedBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
ep, blobBundle, err := c.SubmitBlindedBlock(ctx, sbb, test.Blobs)
|
||||
ep, blobBundle, err := c.SubmitBlindedBlock(ctx, sbb, test.SignedBlindedBlobSidecars)
|
||||
require.NoError(t, err)
|
||||
withdrawals, err := ep.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
@@ -708,9 +754,13 @@ func testSignedBlindedBeaconBlockCapella(t *testing.T) *eth.SignedBlindedBeaconB
|
||||
}
|
||||
|
||||
func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedBeaconBlockAndBlobsDeneb {
|
||||
basebytes, err := shared.Uint256ToSSZBytes("14074904626401341155369551180448584754667373453244490859944217516317499064576")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return ð.SignedBlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: ð.SignedBlindedBeaconBlockDeneb{
|
||||
Block: ð.BlindedBeaconBlockDeneb{
|
||||
SignedBlindedBlock: ð.SignedBlindedBeaconBlockDeneb{
|
||||
Message: ð.BlindedBeaconBlockDeneb{
|
||||
Slot: 1,
|
||||
ProposerIndex: 1,
|
||||
ParentRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
@@ -722,7 +772,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
|
||||
DepositCount: 1,
|
||||
BlockHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
},
|
||||
Graffiti: ezDecode(t, "0xdeadbeefc0ffee"),
|
||||
Graffiti: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
ProposerSlashings: []*eth.ProposerSlashing{
|
||||
{
|
||||
Header_1: ð.SignedBeaconBlockHeader{
|
||||
@@ -825,8 +875,8 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
|
||||
},
|
||||
},
|
||||
SyncAggregate: ð.SyncAggregate{
|
||||
SyncCommitteeSignature: make([]byte, 48),
|
||||
SyncCommitteeBits: bitfield.Bitvector512{0x01},
|
||||
SyncCommitteeSignature: make([]byte, 96),
|
||||
SyncCommitteeBits: bitfield.Bitvector512(ezDecode(t, "0x6451e9f951ebf05edc01de67e593484b672877054f055903ff0df1a1a945cf30ca26bb4d4b154f94a1bc776bcf5d0efb3603e1f9b8ee2499ccdcfe2a18cef458")),
|
||||
},
|
||||
ExecutionPayloadHeader: &v1.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
@@ -840,7 +890,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
|
||||
GasUsed: 1,
|
||||
Timestamp: 1,
|
||||
ExtraData: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
BaseFeePerGas: []byte(strconv.FormatUint(1, 10)),
|
||||
BaseFeePerGas: basebytes,
|
||||
BlockHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
TransactionsRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
WithdrawalsRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
@@ -851,7 +901,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
|
||||
},
|
||||
Signature: ezDecode(t, "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"),
|
||||
},
|
||||
Blobs: []*eth.SignedBlindedBlobSidecar{
|
||||
SignedBlindedBlobSidecars: []*eth.SignedBlindedBlobSidecar{
|
||||
{
|
||||
Message: ð.BlindedBlobSidecar{
|
||||
BlockRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
|
||||
|
||||
@@ -1131,9 +1131,12 @@ func (bb *BuilderBidDeneb) ToProto() (*eth.BuilderBidDeneb, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bundle, err := bb.BlindedBlobsBundle.ToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var bundle *v1.BlindedBlobsBundle
|
||||
if bb.BlindedBlobsBundle != nil {
|
||||
bundle, err = bb.BlindedBlobsBundle.ToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ð.BuilderBidDeneb{
|
||||
Header: header,
|
||||
|
||||
@@ -159,6 +159,36 @@ var testExampleHeaderResponseDeneb = `{
|
||||
}
|
||||
}`
|
||||
|
||||
var testExampleHeaderResponseDenebNoBundle = `{
|
||||
"version": "deneb",
|
||||
"data": {
|
||||
"message": {
|
||||
"header": {
|
||||
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"block_number": "1",
|
||||
"gas_limit": "1",
|
||||
"gas_used": "1",
|
||||
"timestamp": "1",
|
||||
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"base_fee_per_gas": "452312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"blob_gas_used": "1",
|
||||
"excess_blob_gas": "2"
|
||||
},
|
||||
"value": "652312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
}`
|
||||
|
||||
var testExampleHeaderResponseUnknownVersion = `{
|
||||
"version": "bad",
|
||||
"data": {
|
||||
|
||||
@@ -15,6 +15,9 @@ func StartAndEndPage(pageToken string, pageSize, totalSize int) (int, int, strin
|
||||
if pageToken == "" {
|
||||
pageToken = "0"
|
||||
}
|
||||
if pageSize < 0 || totalSize < 0 {
|
||||
return 0, 0, "", errors.Errorf("invalid page and total sizes provided: page size %d , total size %d", pageSize, totalSize)
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = params.BeaconConfig().DefaultPageSize
|
||||
}
|
||||
@@ -23,6 +26,9 @@ func StartAndEndPage(pageToken string, pageSize, totalSize int) (int, int, strin
|
||||
if err != nil {
|
||||
return 0, 0, "", errors.Wrap(err, "could not convert page token")
|
||||
}
|
||||
if token < 0 {
|
||||
return 0, 0, "", errors.Errorf("invalid token value provided: %d", token)
|
||||
}
|
||||
|
||||
// Start page can not be greater than set size.
|
||||
start := token * pageSize
|
||||
|
||||
@@ -85,3 +85,19 @@ func TestStartAndEndPage_ExceedsMaxPage(t *testing.T) {
|
||||
_, _, _, err := pagination.StartAndEndPage("", 0, 0)
|
||||
assert.ErrorContains(t, wanted, err)
|
||||
}
|
||||
|
||||
func TestStartAndEndPage_InvalidPageValues(t *testing.T) {
|
||||
_, _, _, err := pagination.StartAndEndPage("10", -1, 10)
|
||||
assert.ErrorContains(t, "invalid page and total sizes provided", err)
|
||||
|
||||
_, _, _, err = pagination.StartAndEndPage("12", 10, -10)
|
||||
assert.ErrorContains(t, "invalid page and total sizes provided", err)
|
||||
|
||||
_, _, _, err = pagination.StartAndEndPage("12", -50, -60)
|
||||
assert.ErrorContains(t, "invalid page and total sizes provided", err)
|
||||
}
|
||||
|
||||
func TestStartAndEndPage_InvalidTokenValue(t *testing.T) {
|
||||
_, _, _, err := pagination.StartAndEndPage("-12", 50, 60)
|
||||
assert.ErrorContains(t, "invalid token value provided", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"chain_info.go",
|
||||
"chain_info_forkchoice.go",
|
||||
"currently_syncing_block.go",
|
||||
"error.go",
|
||||
"execution_engine.go",
|
||||
"forkchoice_update_execution.go",
|
||||
@@ -38,7 +39,6 @@ go_library(
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/blockchain/kzg:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
|
||||
@@ -525,3 +525,8 @@ func (s *Service) recoverStateSummary(ctx context.Context, blockRoot [32]byte) (
|
||||
}
|
||||
return nil, errBlockDoesNotExist
|
||||
}
|
||||
|
||||
// BlockBeingSynced returns whether the block with the given root is currently being synced
|
||||
func (s *Service) BlockBeingSynced(root [32]byte) bool {
|
||||
return s.blockBeingSynced.isSyncing(root)
|
||||
}
|
||||
|
||||
27
beacon-chain/blockchain/currently_syncing_block.go
Normal file
27
beacon-chain/blockchain/currently_syncing_block.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package blockchain
|
||||
|
||||
import "sync"
|
||||
|
||||
type currentlySyncingBlock struct {
|
||||
sync.Mutex
|
||||
roots map[[32]byte]struct{}
|
||||
}
|
||||
|
||||
func (b *currentlySyncingBlock) set(root [32]byte) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
b.roots[root] = struct{}{}
|
||||
}
|
||||
|
||||
func (b *currentlySyncingBlock) unset(root [32]byte) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
delete(b.roots, root)
|
||||
}
|
||||
|
||||
func (b *currentlySyncingBlock) isSyncing(root [32]byte) bool {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
_, ok := b.roots[root]
|
||||
return ok
|
||||
}
|
||||
@@ -157,6 +157,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *notifyForkcho
|
||||
if hasAttr && payloadID != nil {
|
||||
var pId [8]byte
|
||||
copy(pId[:], payloadID[:])
|
||||
logrus.Infof("Setting payload id for slot %d , index %d and headroot %#x", nextSlot, proposerId, arg.headRoot)
|
||||
s.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(nextSlot, proposerId, pId, arg.headRoot)
|
||||
} else if hasAttr && payloadID == nil && !features.Get().PrepareAllPayloads {
|
||||
log.WithFields(logrus.Fields{
|
||||
@@ -323,6 +324,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
log.WithError(err).Error("Could not get timestamp to get payload attribute")
|
||||
return false, emptyAttri, 0
|
||||
}
|
||||
log.Infof("payload attribute exists for index %d at slot %d with head root %#x", proposerID, slot, headRoot)
|
||||
|
||||
var attr payloadattribute.Attributer
|
||||
switch st.Version() {
|
||||
|
||||
@@ -66,7 +66,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get blob KZG commitments")
|
||||
} else if len(kzgs) > 0 {
|
||||
log = log.WithField("blobCommitmentCount", len(kzgs))
|
||||
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
||||
}
|
||||
}
|
||||
log.Info("Finished applying state transition")
|
||||
@@ -147,3 +147,15 @@ func logPayload(block interfaces.ReadOnlyBeaconBlock) error {
|
||||
log.WithFields(fields).Debug("Synced new payload")
|
||||
return nil
|
||||
}
|
||||
|
||||
func logBlobSidecar(scs []*ethpb.BlobSidecar, startTime time.Time) {
|
||||
if len(scs) == 0 {
|
||||
return
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"count": len(scs),
|
||||
"slot": scs[0].Slot,
|
||||
"block": hex.EncodeToString(scs[0].BlockRoot),
|
||||
"validationTime": time.Since(startTime),
|
||||
}).Debug("Synced new blob sidecars")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package blockchain
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
||||
@@ -62,7 +61,7 @@ func WithExecutionEngineCaller(c execution.EngineCaller) Option {
|
||||
}
|
||||
|
||||
// WithDepositCache for deposit lifecycle after chain inclusion.
|
||||
func WithDepositCache(c *depositcache.DepositCache) Option {
|
||||
func WithDepositCache(c cache.DepositCache) Option {
|
||||
return func(s *Service) error {
|
||||
s.cfg.DepositCache = c
|
||||
return nil
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
coreTime "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
@@ -265,6 +265,9 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.databaseDACheck(ctx, b); err != nil {
|
||||
return errors.Wrap(err, "could not validate blob data availability")
|
||||
}
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b.Block(),
|
||||
JustifiedCheckpoint: jCheckpoints[i],
|
||||
FinalizedCheckpoint: fCheckpoints[i]}
|
||||
@@ -330,6 +333,33 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return s.saveHeadNoDB(ctx, lastB, lastBR, preState, !isValidPayload)
|
||||
}
|
||||
|
||||
func commitmentsToCheck(b consensusblocks.ROBlock, current primitives.Slot) [][]byte {
|
||||
if b.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
// We are only required to check within MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS
|
||||
if !params.WithinDAPeriod(slots.ToEpoch(b.Block().Slot()), slots.ToEpoch(current)) {
|
||||
return nil
|
||||
}
|
||||
kzgCommitments, err := b.Block().Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return kzgCommitments
|
||||
}
|
||||
|
||||
func (s *Service) databaseDACheck(ctx context.Context, b consensusblocks.ROBlock) error {
|
||||
commitments := commitmentsToCheck(b, s.CurrentSlot())
|
||||
if len(commitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, b.Root())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get blob sidecars")
|
||||
}
|
||||
return kzg.IsDataAvailable(commitments, sidecars)
|
||||
}
|
||||
|
||||
func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.BeaconState) error {
|
||||
e := coreTime.CurrentEpoch(st)
|
||||
if err := helpers.UpdateCommitteeCache(ctx, st, e); err != nil {
|
||||
@@ -503,14 +533,17 @@ func (s *Service) isDataAvailable(ctx context.Context, root [32]byte, signed int
|
||||
if signed.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
|
||||
block := signed.Block()
|
||||
if block == nil {
|
||||
return errors.New("invalid nil beacon block")
|
||||
}
|
||||
// We are only required to check within MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS
|
||||
if slots.ToEpoch(block.Slot())+params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest > primitives.Epoch(s.CurrentSlot()) {
|
||||
if !params.WithinDAPeriod(slots.ToEpoch(block.Slot()), slots.ToEpoch(s.CurrentSlot())) {
|
||||
return nil
|
||||
}
|
||||
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
@@ -519,52 +552,52 @@ func (s *Service) isDataAvailable(ctx context.Context, root [32]byte, signed int
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get KZG commitments")
|
||||
}
|
||||
existingBlobs := len(kzgCommitments)
|
||||
if existingBlobs == 0 {
|
||||
expected := len(kzgCommitments)
|
||||
if expected == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read first from db in case we have the blobs
|
||||
s.blobNotifier.Lock()
|
||||
var nc *blobNotifierChan
|
||||
var ok bool
|
||||
nc, ok = s.blobNotifier.chanForRoot[root]
|
||||
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
|
||||
if err == nil {
|
||||
if len(sidecars) >= existingBlobs {
|
||||
delete(s.blobNotifier.chanForRoot, root)
|
||||
s.blobNotifier.Unlock()
|
||||
return kzg.IsDataAvailable(kzgCommitments, sidecars)
|
||||
switch {
|
||||
case err == nil:
|
||||
if len(sidecars) >= expected {
|
||||
s.blobNotifiers.delete(root)
|
||||
if err := kzg.IsDataAvailable(kzgCommitments, sidecars); err != nil {
|
||||
return err
|
||||
}
|
||||
logBlobSidecar(sidecars, t)
|
||||
return nil
|
||||
}
|
||||
case errors.Is(err, db.ErrNotFound):
|
||||
// If the blob sidecars haven't arrived yet, the subsequent code will wait for them.
|
||||
// Note: The system will not exit with an error in this scenario.
|
||||
default:
|
||||
log.WithError(err).Error("could not get blob sidecars from DB")
|
||||
}
|
||||
// Create the channel if it didn't exist already the index map will be
|
||||
// created later anyway
|
||||
if !ok {
|
||||
nc = &blobNotifierChan{channel: make(chan struct{}, fieldparams.MaxBlobsPerBlock)}
|
||||
s.blobNotifier.chanForRoot[root] = nc
|
||||
|
||||
found := map[uint64]struct{}{}
|
||||
for _, sc := range sidecars {
|
||||
found[sc.Index] = struct{}{}
|
||||
}
|
||||
// We have more commitments in the block than blobs in database
|
||||
// We sync the channel indices with the sidecars
|
||||
nc.indices = make(map[uint64]struct{})
|
||||
for _, sidecar := range sidecars {
|
||||
nc.indices[sidecar.Index] = struct{}{}
|
||||
}
|
||||
s.blobNotifier.Unlock()
|
||||
channelWrites := len(sidecars)
|
||||
nc := s.blobNotifiers.forRoot(root)
|
||||
for {
|
||||
select {
|
||||
case <-nc.channel:
|
||||
channelWrites++
|
||||
if channelWrites == existingBlobs {
|
||||
s.blobNotifier.Lock()
|
||||
delete(s.blobNotifier.chanForRoot, root)
|
||||
s.blobNotifier.Unlock()
|
||||
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get blob sidecars")
|
||||
}
|
||||
return kzg.IsDataAvailable(kzgCommitments, sidecars)
|
||||
case idx := <-nc:
|
||||
found[idx] = struct{}{}
|
||||
if len(found) != expected {
|
||||
continue
|
||||
}
|
||||
s.blobNotifiers.delete(root)
|
||||
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get blob sidecars")
|
||||
}
|
||||
if err := kzg.IsDataAvailable(kzgCommitments, sidecars); err != nil {
|
||||
return err
|
||||
}
|
||||
logBlobSidecar(sidecars, t)
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return errors.Wrap(ctx.Err(), "context deadline waiting for blob sidecars")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
|
||||
@@ -235,7 +236,8 @@ func (s *Service) insertFinalizedDeposits(ctx context.Context, fRoot [32]byte) {
|
||||
// to be included(rather than the last one to be processed). This was most likely
|
||||
// done as the state cannot represent signed integers.
|
||||
finalizedEth1DepIdx := eth1DepositIndex - 1
|
||||
if err = s.cfg.DepositCache.InsertFinalizedDeposits(ctx, int64(finalizedEth1DepIdx)); err != nil {
|
||||
if err = s.cfg.DepositCache.InsertFinalizedDeposits(ctx, int64(finalizedEth1DepIdx), common.Hash(finalizedState.Eth1Data().BlockHash),
|
||||
0 /* Setting a zero value as we have no access to block height */); err != nil {
|
||||
log.WithError(err).Error("could not insert finalized deposits")
|
||||
return
|
||||
}
|
||||
@@ -247,7 +249,7 @@ func (s *Service) insertFinalizedDeposits(ctx context.Context, fRoot [32]byte) {
|
||||
// to the provided eth1 deposit index.
|
||||
s.cfg.DepositCache.PrunePendingDeposits(ctx, int64(eth1DepositIndex)) // lint:ignore uintcast -- Deposit index should not exceed int64 in your lifetime.
|
||||
|
||||
log.WithField("duration", time.Since(startTime).String()).Debug("Finalized deposit insertion completed")
|
||||
log.WithField("duration", time.Since(startTime).String()).Debugf("Finalized deposit insertion completed at index %d", finalizedEth1DepIdx)
|
||||
}
|
||||
|
||||
// This ensures that the input root defaults to using genesis root instead of zero hashes. This is needed for handling
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -38,6 +39,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v4/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
@@ -699,7 +701,7 @@ func TestInsertFinalizedDeposits(t *testing.T) {
|
||||
gs, _ := util.DeterministicGenesisState(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
gs = gs.Copy()
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 10}))
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 10, BlockHash: make([]byte, 32)}))
|
||||
assert.NoError(t, gs.SetEth1DepositIndex(8))
|
||||
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
|
||||
var zeroSig [96]byte
|
||||
@@ -713,8 +715,9 @@ func TestInsertFinalizedDeposits(t *testing.T) {
|
||||
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root)))
|
||||
}
|
||||
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
|
||||
fDeposits := depositCache.FinalizedDeposits(ctx)
|
||||
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
|
||||
fDeposits, err := depositCache.FinalizedDeposits(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
|
||||
deps := depositCache.AllDeposits(ctx, big.NewInt(107))
|
||||
for _, d := range deps {
|
||||
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
|
||||
@@ -728,7 +731,7 @@ func TestInsertFinalizedDeposits_PrunePendingDeposits(t *testing.T) {
|
||||
gs, _ := util.DeterministicGenesisState(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
gs = gs.Copy()
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 10}))
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 10, BlockHash: make([]byte, 32)}))
|
||||
assert.NoError(t, gs.SetEth1DepositIndex(8))
|
||||
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
|
||||
var zeroSig [96]byte
|
||||
@@ -748,8 +751,9 @@ func TestInsertFinalizedDeposits_PrunePendingDeposits(t *testing.T) {
|
||||
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root))
|
||||
}
|
||||
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
|
||||
fDeposits := depositCache.FinalizedDeposits(ctx)
|
||||
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
|
||||
fDeposits, err := depositCache.FinalizedDeposits(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
|
||||
deps := depositCache.AllDeposits(ctx, big.NewInt(107))
|
||||
for _, d := range deps {
|
||||
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
|
||||
@@ -767,11 +771,11 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
|
||||
gs, _ := util.DeterministicGenesisState(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
gs = gs.Copy()
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 7}))
|
||||
assert.NoError(t, gs.SetEth1Data(ðpb.Eth1Data{DepositCount: 7, BlockHash: make([]byte, 32)}))
|
||||
assert.NoError(t, gs.SetEth1DepositIndex(6))
|
||||
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
|
||||
gs2 := gs.Copy()
|
||||
assert.NoError(t, gs2.SetEth1Data(ðpb.Eth1Data{DepositCount: 15}))
|
||||
assert.NoError(t, gs2.SetEth1Data(ðpb.Eth1Data{DepositCount: 15, BlockHash: make([]byte, 32)}))
|
||||
assert.NoError(t, gs2.SetEth1DepositIndex(13))
|
||||
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k', '2'}, gs2))
|
||||
var zeroSig [96]byte
|
||||
@@ -785,11 +789,11 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
|
||||
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root)))
|
||||
}
|
||||
// Insert 3 deposits before hand.
|
||||
require.NoError(t, depositCache.InsertFinalizedDeposits(ctx, 2))
|
||||
|
||||
require.NoError(t, depositCache.InsertFinalizedDeposits(ctx, 2, [32]byte{}, 0))
|
||||
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
|
||||
fDeposits := depositCache.FinalizedDeposits(ctx)
|
||||
assert.Equal(t, 5, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
|
||||
fDeposits, err := depositCache.FinalizedDeposits(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
|
||||
|
||||
deps := depositCache.AllDeposits(ctx, big.NewInt(105))
|
||||
for _, d := range deps {
|
||||
@@ -798,8 +802,9 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
|
||||
|
||||
// Insert New Finalized State with higher deposit count.
|
||||
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k', '2'})
|
||||
fDeposits = depositCache.FinalizedDeposits(ctx)
|
||||
assert.Equal(t, 12, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
|
||||
fDeposits, err = depositCache.FinalizedDeposits(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 12, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
|
||||
deps = depositCache.AllDeposits(ctx, big.NewInt(112))
|
||||
for _, d := range deps {
|
||||
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
|
||||
@@ -2033,3 +2038,71 @@ func driftGenesisTime(s *Service, slot, delay int64) {
|
||||
offset := slot*int64(params.BeaconConfig().SecondsPerSlot) - delay
|
||||
s.SetGenesisTime(time.Unix(time.Now().Unix()-offset, 0))
|
||||
}
|
||||
|
||||
func Test_commitmentsToCheck(t *testing.T) {
|
||||
windowSlots, err := slots.EpochEnd(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
|
||||
require.NoError(t, err)
|
||||
commits := [][]byte{
|
||||
bytesutil.PadTo([]byte("a"), 48),
|
||||
bytesutil.PadTo([]byte("b"), 48),
|
||||
bytesutil.PadTo([]byte("c"), 48),
|
||||
bytesutil.PadTo([]byte("d"), 48),
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
commits [][]byte
|
||||
block func(*testing.T) consensusblocks.ROBlock
|
||||
slot primitives.Slot
|
||||
}{
|
||||
{
|
||||
name: "pre deneb",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
bb := util.NewBeaconBlockBellatrix()
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(bb)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "commitments within da",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
d.Block.Slot = 100
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
commits: commits,
|
||||
slot: 100,
|
||||
},
|
||||
{
|
||||
name: "commitments outside da",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
// block is from slot 0, "current slot" is window size +1 (so outside the window)
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
slot: windowSlots + 1,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
b := c.block(t)
|
||||
co := commitmentsToCheck(b, c.slot)
|
||||
require.Equal(t, len(c.commits), len(co))
|
||||
for i := 0; i < len(c.commits); i++ {
|
||||
require.Equal(t, true, bytes.Equal(c.commits[i], co[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
package blockchain
|
||||
|
||||
import fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
|
||||
// SendNewBlobEvent sends a message to the BlobNotifier channel that the blob
|
||||
// for the blocroot `root` is ready in the database
|
||||
func (s *Service) SendNewBlobEvent(root [32]byte, index uint64) {
|
||||
s.blobNotifier.Lock()
|
||||
nc, ok := s.blobNotifier.chanForRoot[root]
|
||||
if !ok {
|
||||
nc = &blobNotifierChan{indices: make(map[uint64]struct{}), channel: make(chan struct{}, fieldparams.MaxBlobsPerBlock)}
|
||||
s.blobNotifier.chanForRoot[root] = nc
|
||||
}
|
||||
_, ok = nc.indices[index]
|
||||
if ok {
|
||||
s.blobNotifier.Unlock()
|
||||
return
|
||||
}
|
||||
nc.indices[index] = struct{}{}
|
||||
s.blobNotifier.Unlock()
|
||||
nc.channel <- struct{}{}
|
||||
s.blobNotifiers.forRoot(root) <- index
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type BlockReceiver interface {
|
||||
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock) error
|
||||
HasBlock(ctx context.Context, root [32]byte) bool
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
BlockBeingSynced([32]byte) bool
|
||||
}
|
||||
|
||||
// BlobReceiver interface defines the methods of chain service for receiving new
|
||||
@@ -58,6 +59,9 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.ReceiveBlock")
|
||||
defer span.End()
|
||||
receivedTime := time.Now()
|
||||
s.blockBeingSynced.set(blockRoot)
|
||||
defer s.blockBeingSynced.unset(blockRoot)
|
||||
|
||||
blockCopy, err := block.Copy()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/kzg"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
@@ -61,7 +61,8 @@ type Service struct {
|
||||
clockSetter startup.ClockSetter
|
||||
clockWaiter startup.ClockWaiter
|
||||
syncComplete chan struct{}
|
||||
blobNotifier *blobNotifier
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
}
|
||||
|
||||
// config options for the service.
|
||||
@@ -69,7 +70,7 @@ type config struct {
|
||||
BeaconBlockBuf int
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
DepositCache *depositcache.DepositCache
|
||||
DepositCache cache.DepositCache
|
||||
ProposerSlotIndexCache *cache.ProposerPayloadIDsCache
|
||||
AttPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
@@ -90,14 +91,26 @@ type config struct {
|
||||
|
||||
var ErrMissingClockSetter = errors.New("blockchain Service initialized without a startup.ClockSetter")
|
||||
|
||||
type blobNotifierChan struct {
|
||||
indices map[uint64]struct{}
|
||||
channel chan struct{}
|
||||
type blobNotifierMap struct {
|
||||
sync.RWMutex
|
||||
notifiers map[[32]byte]chan uint64
|
||||
}
|
||||
|
||||
type blobNotifier struct {
|
||||
sync.RWMutex
|
||||
chanForRoot map[[32]byte]*blobNotifierChan
|
||||
func (bn *blobNotifierMap) forRoot(root [32]byte) chan uint64 {
|
||||
bn.Lock()
|
||||
defer bn.Unlock()
|
||||
c, ok := bn.notifiers[root]
|
||||
if !ok {
|
||||
c = make(chan uint64, fieldparams.MaxBlobsPerBlock)
|
||||
bn.notifiers[root] = c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (bn *blobNotifierMap) delete(root [32]byte) {
|
||||
bn.Lock()
|
||||
defer bn.Unlock()
|
||||
delete(bn.notifiers, root)
|
||||
}
|
||||
|
||||
// NewService instantiates a new block service instance that will
|
||||
@@ -111,8 +124,8 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
bn := &blobNotifier{
|
||||
chanForRoot: make(map[[32]byte]*blobNotifierChan),
|
||||
bn := &blobNotifierMap{
|
||||
notifiers: make(map[[32]byte]chan uint64),
|
||||
}
|
||||
srv := &Service{
|
||||
ctx: ctx,
|
||||
@@ -120,8 +133,9 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
boundaryRoots: [][32]byte{},
|
||||
checkpointStateCache: cache.NewCheckpointStateCache(),
|
||||
initSyncBlocks: make(map[[32]byte]interfaces.ReadOnlySignedBeaconBlock),
|
||||
blobNotifier: bn,
|
||||
blobNotifiers: bn,
|
||||
cfg: &config{ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache()},
|
||||
blockBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(srv); err != nil {
|
||||
@@ -368,7 +382,7 @@ func (s *Service) startFromExecutionChain() error {
|
||||
log.Debug("Context closed, exiting goroutine")
|
||||
return
|
||||
case err := <-stateSub.Err():
|
||||
log.WithError(err).Error("Subscription to state notifier failed")
|
||||
log.WithError(err).Error("Subscription to state forRoot failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ type ChainService struct {
|
||||
FinalizedRoots map[[32]byte]bool
|
||||
OptimisticRoots map[[32]byte]bool
|
||||
BlockSlot primitives.Slot
|
||||
SyncingRoot [32]byte
|
||||
}
|
||||
|
||||
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
|
||||
@@ -606,4 +607,9 @@ func (s *ChainService) UnrealizedJustifiedPayloadBlockHash() [32]byte {
|
||||
}
|
||||
|
||||
// SendNewBlobEvent mocks the same method in the chain service
|
||||
func (s *ChainService) SendNewBlobEvent(_ [32]byte, _ uint64) {}
|
||||
func (*ChainService) SendNewBlobEvent(_ [32]byte, _ uint64) {}
|
||||
|
||||
// BlockBeingSynced mocks the same method in the chain service
|
||||
func (c *ChainService) BlockBeingSynced(root [32]byte) bool {
|
||||
return root == c.SyncingRoot
|
||||
}
|
||||
|
||||
2
beacon-chain/cache/BUILD.bazel
vendored
2
beacon-chain/cache/BUILD.bazel
vendored
@@ -13,6 +13,7 @@ go_library(
|
||||
"common.go",
|
||||
"doc.go",
|
||||
"error.go",
|
||||
"interfaces.go",
|
||||
"payload_id.go",
|
||||
"proposer_indices.go",
|
||||
"proposer_indices_disabled.go", # keep
|
||||
@@ -44,6 +45,7 @@ go_library(
|
||||
"//math:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_hashicorp_golang_lru//:go_default_library",
|
||||
"@com_github_patrickmn_go_cache//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
3
beacon-chain/cache/depositcache/BUILD.bazel
vendored
3
beacon-chain/cache/depositcache/BUILD.bazel
vendored
@@ -13,12 +13,14 @@ go_library(
|
||||
"//testing/spectest:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
@@ -35,6 +37,7 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
@@ -30,20 +32,11 @@ var (
|
||||
})
|
||||
)
|
||||
|
||||
// DepositFetcher defines a struct which can retrieve deposit information from a store.
|
||||
type DepositFetcher interface {
|
||||
AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
|
||||
DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int)
|
||||
DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte)
|
||||
FinalizedDeposits(ctx context.Context) *FinalizedDeposits
|
||||
NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit
|
||||
}
|
||||
|
||||
// FinalizedDeposits stores the trie of deposits that have been included
|
||||
// in the beacon state up to the latest finalized checkpoint.
|
||||
type FinalizedDeposits struct {
|
||||
Deposits *trie.SparseMerkleTrie
|
||||
MerkleTrieIndex int64
|
||||
deposits *trie.SparseMerkleTrie
|
||||
merkleTrieIndex int64
|
||||
}
|
||||
|
||||
// DepositCache stores all in-memory deposit objects. This
|
||||
@@ -52,7 +45,7 @@ type DepositCache struct {
|
||||
// Beacon chain deposits in memory.
|
||||
pendingDeposits []*ethpb.DepositContainer
|
||||
deposits []*ethpb.DepositContainer
|
||||
finalizedDeposits *FinalizedDeposits
|
||||
finalizedDeposits FinalizedDeposits
|
||||
depositsByKey map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer
|
||||
depositsLock sync.RWMutex
|
||||
}
|
||||
@@ -64,13 +57,13 @@ func New() (*DepositCache, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// finalizedDeposits.MerkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
|
||||
// finalizedDeposits.merkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
|
||||
// Inserting the first item into the trie will set the value of the index to 0.
|
||||
return &DepositCache{
|
||||
pendingDeposits: []*ethpb.DepositContainer{},
|
||||
deposits: []*ethpb.DepositContainer{},
|
||||
depositsByKey: map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer{},
|
||||
finalizedDeposits: &FinalizedDeposits{Deposits: finalizedDepositsTrie, MerkleTrieIndex: -1},
|
||||
finalizedDeposits: FinalizedDeposits{deposits: finalizedDepositsTrie, merkleTrieIndex: -1},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -129,14 +122,15 @@ func (dc *DepositCache) InsertDepositContainers(ctx context.Context, ctrs []*eth
|
||||
}
|
||||
|
||||
// InsertFinalizedDeposits inserts deposits up to eth1DepositIndex (inclusive) into the finalized deposits cache.
|
||||
func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64) error {
|
||||
func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context,
|
||||
eth1DepositIndex int64, _ common.Hash, _ uint64) error {
|
||||
ctx, span := trace.StartSpan(ctx, "DepositsCache.InsertFinalizedDeposits")
|
||||
defer span.End()
|
||||
dc.depositsLock.Lock()
|
||||
defer dc.depositsLock.Unlock()
|
||||
|
||||
depositTrie := dc.finalizedDeposits.Deposits
|
||||
insertIndex := int(dc.finalizedDeposits.MerkleTrieIndex + 1)
|
||||
depositTrie := dc.finalizedDeposits.Deposits()
|
||||
insertIndex := int(dc.finalizedDeposits.merkleTrieIndex + 1)
|
||||
|
||||
// Don't insert into finalized trie if there is no deposit to
|
||||
// insert.
|
||||
@@ -154,7 +148,7 @@ func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1Deposit
|
||||
return nil
|
||||
}
|
||||
for _, d := range dc.deposits {
|
||||
if d.Index <= dc.finalizedDeposits.MerkleTrieIndex {
|
||||
if d.Index <= dc.finalizedDeposits.merkleTrieIndex {
|
||||
continue
|
||||
}
|
||||
if d.Index > eth1DepositIndex {
|
||||
@@ -169,10 +163,13 @@ func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1Deposit
|
||||
}
|
||||
insertIndex++
|
||||
}
|
||||
|
||||
dc.finalizedDeposits = &FinalizedDeposits{
|
||||
Deposits: depositTrie,
|
||||
MerkleTrieIndex: eth1DepositIndex,
|
||||
tree, ok := depositTrie.(*trie.SparseMerkleTrie)
|
||||
if !ok {
|
||||
return errors.New("not a sparse merkle tree")
|
||||
}
|
||||
dc.finalizedDeposits = FinalizedDeposits{
|
||||
deposits: tree,
|
||||
merkleTrieIndex: eth1DepositIndex,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -204,8 +201,6 @@ func (dc *DepositCache) AllDepositContainers(ctx context.Context) []*ethpb.Depos
|
||||
// AllDeposits returns a list of historical deposits until the given block number
|
||||
// (inclusive). If no block is specified then this method returns all historical deposits.
|
||||
func (dc *DepositCache) AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
|
||||
ctx, span := trace.StartSpan(ctx, "DepositsCache.AllDeposits")
|
||||
defer span.End()
|
||||
dc.depositsLock.RLock()
|
||||
defer dc.depositsLock.RUnlock()
|
||||
|
||||
@@ -261,16 +256,16 @@ func (dc *DepositCache) DepositByPubkey(ctx context.Context, pubKey []byte) (*et
|
||||
}
|
||||
|
||||
// FinalizedDeposits returns the finalized deposits trie.
|
||||
func (dc *DepositCache) FinalizedDeposits(ctx context.Context) *FinalizedDeposits {
|
||||
func (dc *DepositCache) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "DepositsCache.FinalizedDeposits")
|
||||
defer span.End()
|
||||
dc.depositsLock.RLock()
|
||||
defer dc.depositsLock.RUnlock()
|
||||
|
||||
return &FinalizedDeposits{
|
||||
Deposits: dc.finalizedDeposits.Deposits.Copy(),
|
||||
MerkleTrieIndex: dc.finalizedDeposits.MerkleTrieIndex,
|
||||
}
|
||||
deposits: dc.finalizedDeposits.deposits.Copy(),
|
||||
merkleTrieIndex: dc.finalizedDeposits.merkleTrieIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NonFinalizedDeposits returns the list of non-finalized deposits until the given block number (inclusive).
|
||||
@@ -281,7 +276,7 @@ func (dc *DepositCache) NonFinalizedDeposits(ctx context.Context, lastFinalizedI
|
||||
dc.depositsLock.RLock()
|
||||
defer dc.depositsLock.RUnlock()
|
||||
|
||||
if dc.finalizedDeposits == nil {
|
||||
if dc.finalizedDeposits.Deposits() == nil {
|
||||
return dc.allDeposits(untilBlk)
|
||||
}
|
||||
|
||||
@@ -307,9 +302,6 @@ func (dc *DepositCache) PruneProofs(ctx context.Context, untilDepositIndex int64
|
||||
}
|
||||
|
||||
for i := untilDepositIndex; i >= 0; i-- {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
// Finding a nil proof means that all proofs up to this deposit have been already pruned.
|
||||
if dc.deposits[i].Deposit.Proof == nil {
|
||||
break
|
||||
@@ -319,3 +311,14 @@ func (dc *DepositCache) PruneProofs(ctx context.Context, untilDepositIndex int64
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deposits returns the cached internal deposit tree.
|
||||
func (fd *FinalizedDeposits) Deposits() cache.MerkleTree {
|
||||
return fd.deposits
|
||||
}
|
||||
|
||||
// MerkleTrieIndex represents the last finalized index in
|
||||
// the finalized deposit container.
|
||||
func (fd *FinalizedDeposits) MerkleTrieIndex() int64 {
|
||||
return fd.merkleTrieIndex
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
|
||||
const nilDepositErr = "Ignoring nil deposit insertion"
|
||||
|
||||
var _ DepositFetcher = (*DepositCache)(nil)
|
||||
var _ cache.DepositFetcher = (*DepositCache)(nil)
|
||||
|
||||
func TestInsertDeposit_LogsOnNilDepositInsertion(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
@@ -416,11 +417,12 @@ func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) {
|
||||
Index: 3,
|
||||
})
|
||||
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
|
||||
|
||||
cachedDeposits := dc.FinalizedDeposits(context.Background())
|
||||
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cachedDeposits, "Deposits not cached")
|
||||
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex())
|
||||
|
||||
var deps [][]byte
|
||||
for _, d := range finalizedDeposits {
|
||||
@@ -432,7 +434,7 @@ func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) {
|
||||
require.NoError(t, err, "Could not generate deposit trie")
|
||||
rootA, err := generatedTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
rootB, err := cachedDeposits.Deposits.HashTreeRoot()
|
||||
rootB, err := cachedDeposits.Deposits().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rootA, rootB)
|
||||
}
|
||||
@@ -474,15 +476,16 @@ func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) {
|
||||
Index: 2,
|
||||
}
|
||||
dc.deposits = oldFinalizedDeposits
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
|
||||
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
|
||||
|
||||
dc.deposits = append(dc.deposits, []*ethpb.DepositContainer{newFinalizedDeposit}...)
|
||||
|
||||
cachedDeposits := dc.FinalizedDeposits(context.Background())
|
||||
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cachedDeposits, "Deposits not cached")
|
||||
assert.Equal(t, int64(1), cachedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(1), cachedDeposits.MerkleTrieIndex())
|
||||
|
||||
var deps [][]byte
|
||||
for _, d := range oldFinalizedDeposits {
|
||||
@@ -494,7 +497,7 @@ func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) {
|
||||
require.NoError(t, err, "Could not generate deposit trie")
|
||||
rootA, err := generatedTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
rootB, err := cachedDeposits.Deposits.HashTreeRoot()
|
||||
rootB, err := cachedDeposits.Deposits().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rootA, rootB)
|
||||
}
|
||||
@@ -503,11 +506,12 @@ func TestFinalizedDeposits_HandleZeroDeposits(t *testing.T) {
|
||||
dc, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
|
||||
|
||||
cachedDeposits := dc.FinalizedDeposits(context.Background())
|
||||
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cachedDeposits, "Deposits not cached")
|
||||
assert.Equal(t, int64(-1), cachedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(-1), cachedDeposits.MerkleTrieIndex())
|
||||
}
|
||||
|
||||
func TestFinalizedDeposits_HandleSmallerThanExpectedDeposits(t *testing.T) {
|
||||
@@ -548,11 +552,12 @@ func TestFinalizedDeposits_HandleSmallerThanExpectedDeposits(t *testing.T) {
|
||||
}
|
||||
dc.deposits = finalizedDeposits
|
||||
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5, [32]byte{}, 0))
|
||||
|
||||
cachedDeposits := dc.FinalizedDeposits(context.Background())
|
||||
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cachedDeposits, "Deposits not cached")
|
||||
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex())
|
||||
}
|
||||
|
||||
func TestFinalizedDeposits_HandleLowerEth1DepositIndex(t *testing.T) {
|
||||
@@ -623,14 +628,15 @@ func TestFinalizedDeposits_HandleLowerEth1DepositIndex(t *testing.T) {
|
||||
}
|
||||
dc.deposits = finalizedDeposits
|
||||
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5, [32]byte{}, 0))
|
||||
|
||||
// Reinsert finalized deposits with a lower index.
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
|
||||
|
||||
cachedDeposits := dc.FinalizedDeposits(context.Background())
|
||||
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cachedDeposits, "Deposits not cached")
|
||||
assert.Equal(t, int64(5), cachedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(5), cachedDeposits.MerkleTrieIndex())
|
||||
}
|
||||
|
||||
func TestFinalizedDeposits_InitializedCorrectly(t *testing.T) {
|
||||
@@ -640,7 +646,7 @@ func TestFinalizedDeposits_InitializedCorrectly(t *testing.T) {
|
||||
finalizedDeposits := dc.finalizedDeposits
|
||||
assert.NotNil(t, finalizedDeposits)
|
||||
assert.NotNil(t, finalizedDeposits.Deposits)
|
||||
assert.Equal(t, int64(-1), finalizedDeposits.MerkleTrieIndex)
|
||||
assert.Equal(t, int64(-1), finalizedDeposits.merkleTrieIndex)
|
||||
}
|
||||
|
||||
func TestNonFinalizedDeposits_ReturnsAllNonFinalizedDeposits(t *testing.T) {
|
||||
@@ -694,7 +700,7 @@ func TestNonFinalizedDeposits_ReturnsAllNonFinalizedDeposits(t *testing.T) {
|
||||
},
|
||||
Index: 3,
|
||||
})
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
|
||||
|
||||
deps := dc.NonFinalizedDeposits(context.Background(), 1, nil)
|
||||
assert.Equal(t, 2, len(deps))
|
||||
@@ -751,7 +757,7 @@ func TestNonFinalizedDeposits_ReturnsNonFinalizedDepositsUpToBlockNumber(t *test
|
||||
},
|
||||
Index: 3,
|
||||
})
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
|
||||
|
||||
deps := dc.NonFinalizedDeposits(context.Background(), 1, big.NewInt(10))
|
||||
assert.Equal(t, 1, len(deps))
|
||||
@@ -799,41 +805,43 @@ func TestFinalizedDeposits_ReturnsTrieCorrectly(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Perform this in a non-sensical ordering
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 10))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 3))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 4))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 10, [32]byte{}, 0))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 3, [32]byte{}, 0))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 4, [32]byte{}, 0))
|
||||
|
||||
// Mimic finalized deposit trie fetch.
|
||||
fd := dc.FinalizedDeposits(context.Background())
|
||||
deps := dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex, big.NewInt(14))
|
||||
insertIndex := fd.MerkleTrieIndex + 1
|
||||
fd, err := dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
deps := dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex(), big.NewInt(14))
|
||||
insertIndex := fd.MerkleTrieIndex() + 1
|
||||
|
||||
for _, dep := range deps {
|
||||
depHash, err := dep.Data.HashTreeRoot()
|
||||
assert.NoError(t, err)
|
||||
if err = fd.Deposits.Insert(depHash[:], int(insertIndex)); err != nil {
|
||||
if err = fd.Deposits().Insert(depHash[:], int(insertIndex)); err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
insertIndex++
|
||||
}
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 14))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15, [32]byte{}, 0))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15, [32]byte{}, 0))
|
||||
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 14, [32]byte{}, 0))
|
||||
|
||||
fd = dc.FinalizedDeposits(context.Background())
|
||||
deps = dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex, big.NewInt(30))
|
||||
insertIndex = fd.MerkleTrieIndex + 1
|
||||
fd, err = dc.FinalizedDeposits(context.Background())
|
||||
require.NoError(t, err)
|
||||
deps = dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex(), big.NewInt(30))
|
||||
insertIndex = fd.MerkleTrieIndex() + 1
|
||||
|
||||
for _, dep := range deps {
|
||||
depHash, err := dep.Data.HashTreeRoot()
|
||||
assert.NoError(t, err)
|
||||
if err = fd.Deposits.Insert(depHash[:], int(insertIndex)); err != nil {
|
||||
if err = fd.Deposits().Insert(depHash[:], int(insertIndex)); err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
insertIndex++
|
||||
}
|
||||
assert.Equal(t, fd.Deposits.NumOfItems(), depositTrie.NumOfItems())
|
||||
assert.Equal(t, fd.Deposits().NumOfItems(), depositTrie.NumOfItems())
|
||||
}
|
||||
|
||||
func TestPruneProofs_Ok(t *testing.T) {
|
||||
@@ -1056,3 +1064,11 @@ func makeDepositProof() [][]byte {
|
||||
}
|
||||
return proof
|
||||
}
|
||||
|
||||
func TestEmptyTree(t *testing.T) {
|
||||
finalizedDepositsTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
require.NoError(t, err)
|
||||
v, err := finalizedDepositsTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
fmt.Printf("%x", v)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestInsertPendingDeposit_OK(t *testing.T) {
|
||||
dc := DepositCache{}
|
||||
dc.InsertPendingDeposit(context.Background(), ðpb.Deposit{}, 111, 100, [32]byte{})
|
||||
|
||||
assert.Equal(t, 1, len(dc.pendingDeposits), "Deposit not inserted")
|
||||
assert.Equal(t, 1, len(dc.pendingDeposits), "deposit not inserted")
|
||||
}
|
||||
|
||||
func TestInsertPendingDeposit_ignoresNilDeposit(t *testing.T) {
|
||||
@@ -56,7 +56,7 @@ func TestRemovePendingDeposit_IgnoresNilDeposit(t *testing.T) {
|
||||
dc := DepositCache{}
|
||||
dc.pendingDeposits = []*ethpb.DepositContainer{{Deposit: ðpb.Deposit{}}}
|
||||
dc.RemovePendingDeposit(context.Background(), nil /*deposit*/)
|
||||
assert.Equal(t, 1, len(dc.pendingDeposits), "Deposit unexpectedly removed")
|
||||
assert.Equal(t, 1, len(dc.pendingDeposits), "deposit unexpectedly removed")
|
||||
}
|
||||
|
||||
func TestPendingDeposit_RoundTrip(t *testing.T) {
|
||||
|
||||
23
beacon-chain/cache/depositsnapshot/BUILD.bazel
vendored
23
beacon-chain/cache/depositsnapshot/BUILD.bazel
vendored
@@ -3,26 +3,37 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"deposit_fetcher.go",
|
||||
"deposit_inserter.go",
|
||||
"deposit_tree.go",
|
||||
"deposit_tree_snapshot.go",
|
||||
"merkle_tree.go",
|
||||
"zerohashes.gen.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_wealdtech_go_bytesutil//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"deposit_cache_test.go",
|
||||
"deposit_tree_snapshot_test.go",
|
||||
"merkle_tree_test.go",
|
||||
"spec_test.go",
|
||||
@@ -32,10 +43,16 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@in_gopkg_yaml_v3//:go_default_library",
|
||||
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||
|
||||
1199
beacon-chain/cache/depositsnapshot/deposit_cache_test.go
vendored
Normal file
1199
beacon-chain/cache/depositsnapshot/deposit_cache_test.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
304
beacon-chain/cache/depositsnapshot/deposit_fetcher.go
vendored
Normal file
304
beacon-chain/cache/depositsnapshot/deposit_fetcher.go
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
package depositsnapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
pendingDepositsCount = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "beacondb_pending_deposits_eip4881",
|
||||
Help: "The number of pending deposits in memory",
|
||||
})
|
||||
)
|
||||
|
||||
// Cache stores all in-memory deposit objects. This
|
||||
// stores all the deposit related data that is required by the beacon-node.
|
||||
type Cache struct {
|
||||
pendingDeposits []*ethpb.DepositContainer
|
||||
deposits []*ethpb.DepositContainer
|
||||
finalizedDeposits finalizedDepositsContainer
|
||||
depositsByKey map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer
|
||||
depositsLock sync.RWMutex
|
||||
}
|
||||
|
||||
// finalizedDepositsContainer stores the trie of deposits that have been included
|
||||
// in the beacon state up to the latest finalized checkpoint.
|
||||
type finalizedDepositsContainer struct {
|
||||
depositTree *DepositTree
|
||||
merkleTrieIndex int64
|
||||
}
|
||||
|
||||
// New instantiates a new deposit cache
|
||||
func New() (*Cache, error) {
|
||||
finalizedDepositsTrie := NewDepositTree()
|
||||
|
||||
// finalizedDeposits.merkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
|
||||
// Inserting the first item into the trie will set the value of the index to 0.
|
||||
return &Cache{
|
||||
pendingDeposits: []*ethpb.DepositContainer{},
|
||||
deposits: []*ethpb.DepositContainer{},
|
||||
depositsByKey: map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer{},
|
||||
finalizedDeposits: toFinalizedDepositsContainer(finalizedDepositsTrie, -1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AllDeposits returns a list of historical deposits until the given block number
|
||||
// (inclusive). If no block is specified then this method returns all historical deposits.
|
||||
func (c *Cache) AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
return c.allDeposits(untilBlk)
|
||||
}
|
||||
|
||||
func (c *Cache) allDeposits(untilBlk *big.Int) []*ethpb.Deposit {
|
||||
var deposits []*ethpb.Deposit
|
||||
for _, ctnr := range c.deposits {
|
||||
cBlk := big.NewInt(0).SetUint64(ctnr.Eth1BlockHeight)
|
||||
if untilBlk == nil || untilBlk.Cmp(cBlk) >= 0 {
|
||||
deposits = append(deposits, ctnr.Deposit)
|
||||
}
|
||||
}
|
||||
return deposits
|
||||
}
|
||||
|
||||
// AllDepositContainers returns all historical deposit containers.
|
||||
func (c *Cache) AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.AllDepositContainers")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
// Make a shallow copy of the deposits and return that. This way, the
|
||||
// caller can safely iterate over the returned list of deposits without
|
||||
// the possibility of new deposits showing up. If we were to return the
|
||||
// list without a copy, when a new deposit is added to the cache, it
|
||||
// would also be present in the returned value. This could result in a
|
||||
// race condition if the list is being iterated over.
|
||||
//
|
||||
// It's not necessary to make a deep copy of this list because the
|
||||
// deposits in the cache should never be modified. It is still possible
|
||||
// for the caller to modify one of the underlying deposits and modify
|
||||
// the cache, but that's not a race condition. Also, a deep copy would
|
||||
// take too long and use too much memory.
|
||||
deposits := make([]*ethpb.DepositContainer, len(c.deposits))
|
||||
copy(deposits, c.deposits)
|
||||
return deposits
|
||||
}
|
||||
|
||||
// DepositByPubkey looks through historical deposits and finds one which contains
|
||||
// a certain public key within its deposit data.
|
||||
func (c *Cache) DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.DepositByPubkey")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
var deposit *ethpb.Deposit
|
||||
var blockNum *big.Int
|
||||
deps, ok := c.depositsByKey[bytesutil.ToBytes48(pubKey)]
|
||||
if !ok || len(deps) == 0 {
|
||||
return deposit, blockNum
|
||||
}
|
||||
// We always return the first deposit if a particular
|
||||
// validator key has multiple deposits assigned to
|
||||
// it.
|
||||
deposit = deps[0].Deposit
|
||||
blockNum = big.NewInt(int64(deps[0].Eth1BlockHeight))
|
||||
return deposit, blockNum
|
||||
}
|
||||
|
||||
// DepositsNumberAndRootAtHeight returns number of deposits made up to blockheight and the
|
||||
// root that corresponds to the latest deposit at that blockheight.
|
||||
func (c *Cache) DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.DepositsNumberAndRootAtHeight")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
heightIdx := sort.Search(len(c.deposits), func(i int) bool {
|
||||
dBlkHeight := big.NewInt(0).SetUint64(c.deposits[i].Eth1BlockHeight)
|
||||
return dBlkHeight.Cmp(blockHeight) > 0
|
||||
})
|
||||
// send the deposit root of the empty trie, if eth1follow distance is greater than the time of the earliest
|
||||
// deposit.
|
||||
if heightIdx == 0 {
|
||||
return 0, [32]byte{}
|
||||
}
|
||||
return uint64(heightIdx), bytesutil.ToBytes32(c.deposits[heightIdx-1].DepositRoot)
|
||||
}
|
||||
|
||||
// FinalizedDeposits returns the finalized deposits trie.
|
||||
func (c *Cache) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.FinalizedDeposits")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
tree, err := c.finalizedDeposits.depositTree.Copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &finalizedDepositsContainer{
|
||||
depositTree: tree,
|
||||
merkleTrieIndex: c.finalizedDeposits.merkleTrieIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NonFinalizedDeposits returns the list of non-finalized deposits until the given block number (inclusive).
|
||||
// If no block is specified then this method returns all non-finalized deposits.
|
||||
func (c *Cache) NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.NonFinalizedDeposits")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
if c.finalizedDeposits.depositTree == nil {
|
||||
return c.allDeposits(untilBlk)
|
||||
}
|
||||
|
||||
var deposits []*ethpb.Deposit
|
||||
for _, d := range c.deposits {
|
||||
if (d.Index > lastFinalizedIndex) && (untilBlk == nil || untilBlk.Uint64() >= d.Eth1BlockHeight) {
|
||||
deposits = append(deposits, d.Deposit)
|
||||
}
|
||||
}
|
||||
|
||||
return deposits
|
||||
}
|
||||
|
||||
// PruneProofs removes proofs from all deposits whose index is equal or less than untilDepositIndex.
|
||||
func (c *Cache) PruneProofs(ctx context.Context, untilDepositIndex int64) error {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.PruneProofs")
|
||||
defer span.End()
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
|
||||
if untilDepositIndex >= int64(len(c.deposits)) {
|
||||
untilDepositIndex = int64(len(c.deposits) - 1)
|
||||
}
|
||||
|
||||
for i := untilDepositIndex; i >= 0; i-- {
|
||||
// Finding a nil proof means that all proofs up to this deposit have been already pruned.
|
||||
if c.deposits[i].Deposit.Proof == nil {
|
||||
break
|
||||
}
|
||||
c.deposits[i].Deposit.Proof = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrunePendingDeposits removes any deposit which is older than the given deposit merkle tree index.
|
||||
func (c *Cache) PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.PrunePendingDeposits")
|
||||
defer span.End()
|
||||
|
||||
if merkleTreeIndex == 0 {
|
||||
log.Debug("Ignoring 0 deposit removal")
|
||||
return
|
||||
}
|
||||
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
|
||||
cleanDeposits := make([]*ethpb.DepositContainer, 0, len(c.pendingDeposits))
|
||||
for _, dp := range c.pendingDeposits {
|
||||
if dp.Index >= merkleTreeIndex {
|
||||
cleanDeposits = append(cleanDeposits, dp)
|
||||
}
|
||||
}
|
||||
|
||||
c.pendingDeposits = cleanDeposits
|
||||
pendingDepositsCount.Set(float64(len(c.pendingDeposits)))
|
||||
}
|
||||
|
||||
// InsertPendingDeposit into the database. If deposit or block number are nil
|
||||
// then this method does nothing.
|
||||
func (c *Cache) InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.InsertPendingDeposit")
|
||||
defer span.End()
|
||||
if d == nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"block": blockNum,
|
||||
"deposit": d,
|
||||
}).Debug("Ignoring nil deposit insertion")
|
||||
return
|
||||
}
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
c.pendingDeposits = append(c.pendingDeposits,
|
||||
ðpb.DepositContainer{Deposit: d, Eth1BlockHeight: blockNum, Index: index, DepositRoot: depositRoot[:]})
|
||||
pendingDepositsCount.Set(float64(len(c.pendingDeposits)))
|
||||
span.AddAttributes(trace.Int64Attribute("count", int64(len(c.pendingDeposits))))
|
||||
}
|
||||
|
||||
// Deposits returns the cached internal deposit tree.
|
||||
func (fd *finalizedDepositsContainer) Deposits() cache.MerkleTree {
|
||||
return fd.depositTree
|
||||
}
|
||||
|
||||
// MerkleTrieIndex represents the last finalized index in
|
||||
// the finalized deposit container.
|
||||
func (fd *finalizedDepositsContainer) MerkleTrieIndex() int64 {
|
||||
return fd.merkleTrieIndex
|
||||
}
|
||||
|
||||
func toFinalizedDepositsContainer(deposits *DepositTree, index int64) finalizedDepositsContainer {
|
||||
return finalizedDepositsContainer{
|
||||
depositTree: deposits,
|
||||
merkleTrieIndex: index,
|
||||
}
|
||||
}
|
||||
|
||||
// PendingDeposits returns a list of deposits until the given block number
|
||||
// (inclusive). If no block is specified then this method returns all pending
|
||||
// deposits.
|
||||
func (c *Cache) PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.PendingDeposits")
|
||||
defer span.End()
|
||||
|
||||
depositCntrs := c.PendingContainers(ctx, untilBlk)
|
||||
|
||||
deposits := make([]*ethpb.Deposit, 0, len(depositCntrs))
|
||||
for _, dep := range depositCntrs {
|
||||
deposits = append(deposits, dep.Deposit)
|
||||
}
|
||||
|
||||
return deposits
|
||||
}
|
||||
|
||||
// PendingContainers returns a list of deposit containers until the given block number
|
||||
// (inclusive).
|
||||
func (c *Cache) PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.PendingContainers")
|
||||
defer span.End()
|
||||
c.depositsLock.RLock()
|
||||
defer c.depositsLock.RUnlock()
|
||||
|
||||
depositCntrs := make([]*ethpb.DepositContainer, 0, len(c.pendingDeposits))
|
||||
for _, ctnr := range c.pendingDeposits {
|
||||
if untilBlk == nil || untilBlk.Uint64() >= ctnr.Eth1BlockHeight {
|
||||
depositCntrs = append(depositCntrs, ctnr)
|
||||
}
|
||||
}
|
||||
// Sort the deposits by Merkle index.
|
||||
sort.SliceStable(depositCntrs, func(i, j int) bool {
|
||||
return depositCntrs[i].Index < depositCntrs[j].Index
|
||||
})
|
||||
|
||||
span.AddAttributes(trace.Int64Attribute("count", int64(len(depositCntrs))))
|
||||
|
||||
return depositCntrs
|
||||
}
|
||||
134
beacon-chain/cache/depositsnapshot/deposit_inserter.go
vendored
Normal file
134
beacon-chain/cache/depositsnapshot/deposit_inserter.go
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package depositsnapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
historicalDepositsCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "beacondb_all_deposits_eip4881",
|
||||
Help: "The number of total deposits in memory",
|
||||
})
|
||||
log = logrus.WithField("prefix", "cache")
|
||||
)
|
||||
|
||||
// InsertDeposit into the database. If deposit or block number are nil
|
||||
// then this method does nothing.
|
||||
func (c *Cache) InsertDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) error {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.InsertDeposit")
|
||||
defer span.End()
|
||||
if d == nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"block": blockNum,
|
||||
"deposit": d,
|
||||
"index": index,
|
||||
"deposit root": hex.EncodeToString(depositRoot[:]),
|
||||
}).Warn("Ignoring nil deposit insertion")
|
||||
return errors.New("nil deposit inserted into the cache")
|
||||
}
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
|
||||
if int(index) != len(c.deposits) {
|
||||
return errors.Errorf("wanted deposit with index %d to be inserted but received %d", len(c.deposits), index)
|
||||
}
|
||||
// Keep the slice sorted on insertion in order to avoid costly sorting on retrieval.
|
||||
heightIdx := sort.Search(len(c.deposits), func(i int) bool { return c.deposits[i].Index >= index })
|
||||
depCtr := ðpb.DepositContainer{Deposit: d, Eth1BlockHeight: blockNum, DepositRoot: depositRoot[:], Index: index}
|
||||
newDeposits := append(
|
||||
[]*ethpb.DepositContainer{depCtr},
|
||||
c.deposits[heightIdx:]...)
|
||||
c.deposits = append(c.deposits[:heightIdx], newDeposits...)
|
||||
// Append the deposit to our map, in the event no deposits
|
||||
// exist for the pubkey , it is simply added to the map.
|
||||
pubkey := bytesutil.ToBytes48(d.Data.PublicKey)
|
||||
c.depositsByKey[pubkey] = append(c.depositsByKey[pubkey], depCtr)
|
||||
historicalDepositsCount.Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertDepositContainers inserts a set of deposit containers into our deposit cache.
|
||||
func (c *Cache) InsertDepositContainers(ctx context.Context, ctrs []*ethpb.DepositContainer) {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.InsertDepositContainers")
|
||||
defer span.End()
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
|
||||
// Initialize slice if nil object provided.
|
||||
if ctrs == nil {
|
||||
ctrs = make([]*ethpb.DepositContainer, 0)
|
||||
}
|
||||
sort.SliceStable(ctrs, func(i int, j int) bool { return ctrs[i].Index < ctrs[j].Index })
|
||||
c.deposits = ctrs
|
||||
for _, ctr := range ctrs {
|
||||
// Use a new value, as the reference
|
||||
// changes in the next iteration.
|
||||
newPtr := ctr
|
||||
pKey := bytesutil.ToBytes48(newPtr.Deposit.Data.PublicKey)
|
||||
c.depositsByKey[pKey] = append(c.depositsByKey[pKey], newPtr)
|
||||
}
|
||||
historicalDepositsCount.Add(float64(len(ctrs)))
|
||||
}
|
||||
|
||||
// InsertFinalizedDeposits inserts deposits up to eth1DepositIndex (inclusive) into the finalized deposits cache.
|
||||
func (c *Cache) InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64,
|
||||
executionHash common.Hash, executionNumber uint64) error {
|
||||
ctx, span := trace.StartSpan(ctx, "Cache.InsertFinalizedDeposits")
|
||||
defer span.End()
|
||||
c.depositsLock.Lock()
|
||||
defer c.depositsLock.Unlock()
|
||||
|
||||
depositTrie := c.finalizedDeposits.depositTree
|
||||
insertIndex := int(c.finalizedDeposits.MerkleTrieIndex() + 1)
|
||||
|
||||
// Don't insert into finalized trie if there is no deposit to
|
||||
// insert.
|
||||
if len(c.deposits) == 0 {
|
||||
return nil
|
||||
}
|
||||
// In the event we have less deposits than we need to
|
||||
// finalize we finalize till the index on which we do have it.
|
||||
if len(c.deposits) <= int(eth1DepositIndex) {
|
||||
eth1DepositIndex = int64(len(c.deposits)) - 1
|
||||
}
|
||||
// If we finalize to some lower deposit index, we
|
||||
// ignore it.
|
||||
if int(eth1DepositIndex) < insertIndex {
|
||||
return nil
|
||||
}
|
||||
currIdx := int64(depositTrie.depositCount) - 1
|
||||
|
||||
// Insert deposits into deposit trie.
|
||||
for _, ctr := range c.deposits {
|
||||
if ctr.Index > currIdx && ctr.Index <= eth1DepositIndex {
|
||||
rt, err := ctr.Deposit.Data.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := depositTrie.Insert(rt[:], int(ctr.Index)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := depositTrie.Finalize(eth1DepositIndex, executionHash, executionNumber); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.finalizedDeposits = finalizedDepositsContainer{
|
||||
depositTree: depositTrie,
|
||||
merkleTrieIndex: eth1DepositIndex,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
138
beacon-chain/cache/depositsnapshot/deposit_tree.go
vendored
138
beacon-chain/cache/depositsnapshot/deposit_tree.go
vendored
@@ -4,12 +4,14 @@
|
||||
package depositsnapshot
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/math"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
protodb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,12 +19,10 @@ var (
|
||||
ErrEmptyExecutionBlock = errors.New("empty execution block")
|
||||
// ErrInvalidSnapshotRoot occurs when the snapshot root does not match the calculated root.
|
||||
ErrInvalidSnapshotRoot = errors.New("snapshot root is invalid")
|
||||
// ErrInvalidMixInLength occurs when the value for mix in length is 0.
|
||||
ErrInvalidMixInLength = errors.New("mixInLength should be greater than 0")
|
||||
// ErrInvalidDepositCount occurs when the value for mix in length is 0.
|
||||
ErrInvalidDepositCount = errors.New("deposit count should be greater than 0")
|
||||
// ErrInvalidIndex occurs when the index is less than the number of finalized deposits.
|
||||
ErrInvalidIndex = errors.New("index should be greater than finalizedDeposits - 1")
|
||||
// ErrNoDeposits occurs when the number of deposits is 0.
|
||||
ErrNoDeposits = errors.New("number of deposits should be greater than 0")
|
||||
// ErrTooManyDeposits occurs when the number of deposits exceeds the capacity of the tree.
|
||||
ErrTooManyDeposits = errors.New("number of deposits should not be greater than the capacity of the tree")
|
||||
)
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
// DepositTree is the Merkle tree representation of deposits.
|
||||
type DepositTree struct {
|
||||
tree MerkleTreeNode
|
||||
mixInLength uint64 // number of deposits in the tree, reference implementation calls this mix_in_length.
|
||||
depositCount uint64 // number of deposits in the tree, reference implementation calls this mix_in_length.
|
||||
finalizedExecutionBlock executionBlock
|
||||
}
|
||||
|
||||
@@ -39,82 +39,67 @@ type executionBlock struct {
|
||||
Depth uint64
|
||||
}
|
||||
|
||||
// New creates an empty deposit tree.
|
||||
//
|
||||
//nolint:unused
|
||||
func New() *DepositTree {
|
||||
// NewDepositTree creates an empty deposit tree.
|
||||
func NewDepositTree() *DepositTree {
|
||||
var leaves [][32]byte
|
||||
merkle := create(leaves, DepositContractDepth)
|
||||
return &DepositTree{
|
||||
tree: merkle,
|
||||
mixInLength: 0,
|
||||
depositCount: 0,
|
||||
finalizedExecutionBlock: executionBlock{},
|
||||
}
|
||||
}
|
||||
|
||||
// getSnapshot returns a deposit tree snapshot.
|
||||
//
|
||||
//nolint:unused
|
||||
func (d *DepositTree) getSnapshot() (DepositTreeSnapshot, error) {
|
||||
if d.finalizedExecutionBlock == (executionBlock{}) {
|
||||
return DepositTreeSnapshot{}, ErrEmptyExecutionBlock
|
||||
}
|
||||
// GetSnapshot returns a deposit tree snapshot.
|
||||
func (d *DepositTree) GetSnapshot() (DepositTreeSnapshot, error) {
|
||||
var finalized [][32]byte
|
||||
depositCount, finalized := d.tree.GetFinalized(finalized)
|
||||
return fromTreeParts(finalized, depositCount, d.finalizedExecutionBlock)
|
||||
}
|
||||
|
||||
// fromSnapshot returns a deposit tree from a deposit tree snapshot.
|
||||
//
|
||||
//nolint:unused
|
||||
func fromSnapshot(snapshot DepositTreeSnapshot) (DepositTree, error) {
|
||||
func fromSnapshot(snapshot DepositTreeSnapshot) (*DepositTree, error) {
|
||||
root, err := snapshot.CalculateRoot()
|
||||
if err != nil {
|
||||
return DepositTree{}, err
|
||||
return nil, err
|
||||
}
|
||||
if snapshot.depositRoot != root {
|
||||
return DepositTree{}, ErrInvalidSnapshotRoot
|
||||
return nil, ErrInvalidSnapshotRoot
|
||||
}
|
||||
if snapshot.depositCount >= math.PowerOf2(uint64(DepositContractDepth)) {
|
||||
return DepositTree{}, ErrTooManyDeposits
|
||||
return nil, ErrTooManyDeposits
|
||||
}
|
||||
tree, err := fromSnapshotParts(snapshot.finalized, snapshot.depositCount, DepositContractDepth)
|
||||
if err != nil {
|
||||
return DepositTree{}, err
|
||||
return nil, err
|
||||
}
|
||||
if snapshot.depositCount == 0 {
|
||||
return DepositTree{}, ErrNoDeposits
|
||||
}
|
||||
return DepositTree{
|
||||
return &DepositTree{
|
||||
tree: tree,
|
||||
mixInLength: snapshot.depositCount,
|
||||
depositCount: snapshot.depositCount,
|
||||
finalizedExecutionBlock: snapshot.executionBlock,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// finalize marks a deposit as finalized.
|
||||
//
|
||||
//nolint:unused
|
||||
func (d *DepositTree) finalize(eth1data *eth.Eth1Data, executionBlockHeight uint64) error {
|
||||
// Finalize marks a deposit as finalized.
|
||||
func (d *DepositTree) Finalize(eth1DepositIndex int64, executionHash common.Hash, executionNumber uint64) error {
|
||||
var blockHash [32]byte
|
||||
copy(blockHash[:], eth1data.BlockHash)
|
||||
copy(blockHash[:], executionHash[:])
|
||||
d.finalizedExecutionBlock = executionBlock{
|
||||
Hash: blockHash,
|
||||
Depth: executionBlockHeight,
|
||||
Depth: executionNumber,
|
||||
}
|
||||
_, err := d.tree.Finalize(eth1data.DepositCount, DepositContractDepth)
|
||||
depositCount := uint64(eth1DepositIndex + 1)
|
||||
_, err := d.tree.Finalize(depositCount, DepositContractDepth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getProof returns the Deposit tree proof.
|
||||
//
|
||||
//nolint:unused
|
||||
// getProof returns the deposit tree proof.
|
||||
func (d *DepositTree) getProof(index uint64) ([32]byte, [][32]byte, error) {
|
||||
if d.mixInLength <= 0 {
|
||||
return [32]byte{}, nil, ErrInvalidMixInLength
|
||||
if d.depositCount <= 0 {
|
||||
return [32]byte{}, nil, ErrInvalidDepositCount
|
||||
}
|
||||
finalizedDeposits, _ := d.tree.GetFinalized([][32]byte{})
|
||||
if finalizedDeposits != 0 {
|
||||
@@ -125,28 +110,81 @@ func (d *DepositTree) getProof(index uint64) ([32]byte, [][32]byte, error) {
|
||||
}
|
||||
leaf, proof := generateProof(d.tree, index, DepositContractDepth)
|
||||
var mixInLength [32]byte
|
||||
copy(mixInLength[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength))
|
||||
copy(mixInLength[:], bytesutil.Uint64ToBytesLittleEndian32(d.depositCount))
|
||||
proof = append(proof, mixInLength)
|
||||
return leaf, proof, nil
|
||||
}
|
||||
|
||||
// getRoot returns the root of the deposit tree.
|
||||
//
|
||||
//nolint:unused
|
||||
func (d *DepositTree) getRoot() [32]byte {
|
||||
var enc [32]byte
|
||||
binary.LittleEndian.PutUint64(enc[:], d.depositCount)
|
||||
|
||||
root := d.tree.GetRoot()
|
||||
return sha256.Sum256(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength)...))
|
||||
return hash.Hash(append(root[:], enc[:]...))
|
||||
}
|
||||
|
||||
// pushLeaf adds a new leaf to the tree.
|
||||
//
|
||||
//nolint:unused
|
||||
func (d *DepositTree) pushLeaf(leaf [32]byte) error {
|
||||
var err error
|
||||
d.tree, err = d.tree.PushLeaf(leaf, DepositContractDepth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.mixInLength++
|
||||
d.depositCount++
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert is defined as part of MerkleTree interface and adds a new leaf to the tree.
|
||||
func (d *DepositTree) Insert(item []byte, _ int) error {
|
||||
var leaf [32]byte
|
||||
copy(leaf[:], item[:32])
|
||||
return d.pushLeaf(leaf)
|
||||
}
|
||||
|
||||
// HashTreeRoot is defined as part of MerkleTree interface and calculates the hash tree root.
|
||||
func (d *DepositTree) HashTreeRoot() ([32]byte, error) {
|
||||
root := d.getRoot()
|
||||
if root == [32]byte{} {
|
||||
return [32]byte{}, errors.New("could not retrieve hash tree root")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// NumOfItems is defined as part of MerkleTree interface and returns the number of deposits in the tree.
|
||||
func (d *DepositTree) NumOfItems() int {
|
||||
return int(d.depositCount)
|
||||
}
|
||||
|
||||
// MerkleProof is defined as part of MerkleTree interface and generates a merkle proof.
|
||||
func (d *DepositTree) MerkleProof(index int) ([][]byte, error) {
|
||||
_, proof, err := d.getProof(uint64(index))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byteSlices := make([][]byte, len(proof))
|
||||
for i, p := range proof {
|
||||
copied := p
|
||||
byteSlices[i] = copied[:]
|
||||
}
|
||||
return byteSlices, nil
|
||||
}
|
||||
|
||||
// Copy performs a deep copy of the tree.
|
||||
func (d *DepositTree) Copy() (*DepositTree, error) {
|
||||
snapshot, err := d.GetSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromSnapshot(snapshot)
|
||||
}
|
||||
|
||||
// ToProto returns a proto object of the deposit snapshot of
|
||||
// the tree.
|
||||
func (d *DepositTree) ToProto() (*protodb.DepositSnapshot, error) {
|
||||
snapshot, err := d.GetSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snapshot.ToProto(), nil
|
||||
}
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
package depositsnapshot
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
protodb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrZeroIndex occurs when the value of index is 0.
|
||||
ErrZeroIndex = errors.New("index should be greater than 0")
|
||||
)
|
||||
|
||||
// DepositTreeSnapshot represents the data used to create a
|
||||
// deposit tree given a snapshot.
|
||||
//
|
||||
//nolint:unused
|
||||
// DepositTreeSnapshot represents the data used to create a deposit tree given a snapshot.
|
||||
type DepositTreeSnapshot struct {
|
||||
finalized [][32]byte
|
||||
depositRoot [32]byte
|
||||
@@ -27,29 +19,27 @@ type DepositTreeSnapshot struct {
|
||||
func (ds *DepositTreeSnapshot) CalculateRoot() ([32]byte, error) {
|
||||
size := ds.depositCount
|
||||
index := len(ds.finalized)
|
||||
root := Zerohashes[0]
|
||||
root := trie.ZeroHashes[0]
|
||||
for i := 0; i < DepositContractDepth; i++ {
|
||||
if (size & 1) == 1 {
|
||||
if index == 0 {
|
||||
return [32]byte{}, ErrZeroIndex
|
||||
break
|
||||
}
|
||||
index--
|
||||
root = sha256.Sum256(append(ds.finalized[index][:], root[:]...))
|
||||
root = hash.Hash(append(ds.finalized[index][:], root[:]...))
|
||||
} else {
|
||||
root = sha256.Sum256(append(root[:], Zerohashes[i][:]...))
|
||||
root = hash.Hash(append(root[:], trie.ZeroHashes[i][:]...))
|
||||
}
|
||||
size >>= 1
|
||||
}
|
||||
return sha256.Sum256(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(ds.depositCount)...)), nil
|
||||
return hash.Hash(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(ds.depositCount)...)), nil
|
||||
}
|
||||
|
||||
// fromTreeParts constructs the deposit tree from pre-existing data.
|
||||
//
|
||||
//nolint:unused
|
||||
func fromTreeParts(finalised [][32]byte, depositCount uint64, executionBlock executionBlock) (DepositTreeSnapshot, error) {
|
||||
snapshot := DepositTreeSnapshot{
|
||||
finalized: finalised,
|
||||
depositRoot: Zerohashes[0],
|
||||
depositRoot: trie.ZeroHashes[0],
|
||||
depositCount: depositCount,
|
||||
executionBlock: executionBlock,
|
||||
}
|
||||
@@ -60,3 +50,36 @@ func fromTreeParts(finalised [][32]byte, depositCount uint64, executionBlock exe
|
||||
snapshot.depositRoot = root
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
// ToProto converts the underlying trie into its corresponding proto object.
|
||||
func (ds *DepositTreeSnapshot) ToProto() *protodb.DepositSnapshot {
|
||||
tree := &protodb.DepositSnapshot{
|
||||
Finalized: make([][]byte, len(ds.finalized)),
|
||||
DepositRoot: bytesutil.SafeCopyBytes(ds.depositRoot[:]),
|
||||
DepositCount: ds.depositCount,
|
||||
ExecutionHash: bytesutil.SafeCopyBytes(ds.executionBlock.Hash[:]),
|
||||
ExecutionDepth: ds.executionBlock.Depth,
|
||||
}
|
||||
for i := range ds.finalized {
|
||||
tree.Finalized[i] = bytesutil.SafeCopyBytes(ds.finalized[i][:])
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// DepositTreeFromSnapshotProto generates a deposit tree object from a provided snapshot.
|
||||
func DepositTreeFromSnapshotProto(snapshotProto *protodb.DepositSnapshot) (*DepositTree, error) {
|
||||
finalized := make([][32]byte, len(snapshotProto.Finalized))
|
||||
for i := range snapshotProto.Finalized {
|
||||
finalized[i] = bytesutil.ToBytes32(snapshotProto.Finalized[i])
|
||||
}
|
||||
snapshot := DepositTreeSnapshot{
|
||||
finalized: finalized,
|
||||
depositRoot: bytesutil.ToBytes32(snapshotProto.DepositRoot),
|
||||
depositCount: snapshotProto.DepositCount,
|
||||
executionBlock: executionBlock{
|
||||
Hash: bytesutil.ToBytes32(snapshotProto.ExecutionHash),
|
||||
Depth: snapshotProto.ExecutionDepth,
|
||||
},
|
||||
}
|
||||
return fromSnapshot(snapshot)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package depositsnapshot
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/slice"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/math"
|
||||
)
|
||||
@@ -58,9 +59,8 @@ func create(leaves [][32]byte, depth uint64) MerkleTreeNode {
|
||||
}
|
||||
|
||||
// fromSnapshotParts creates a new Merkle tree from a list of finalized leaves, number of deposits and specified depth.
|
||||
//
|
||||
//nolint:unused
|
||||
func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (_ MerkleTreeNode, err error) {
|
||||
func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (MerkleTreeNode, error) {
|
||||
var err error
|
||||
if len(finalized) < 1 || deposits == 0 {
|
||||
return &ZeroNode{
|
||||
depth: level,
|
||||
@@ -96,8 +96,6 @@ func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (_ M
|
||||
}
|
||||
|
||||
// generateProof returns a merkle proof and root
|
||||
//
|
||||
//nolint:unused
|
||||
func generateProof(tree MerkleTreeNode, index uint64, depth uint64) ([32]byte, [][32]byte) {
|
||||
var proof [][32]byte
|
||||
node := tree
|
||||
@@ -219,7 +217,8 @@ func (n *InnerNode) IsFull() bool {
|
||||
}
|
||||
|
||||
// Finalize marks deposits of the Merkle tree as finalized.
|
||||
func (n *InnerNode) Finalize(depositsToFinalize uint64, depth uint64) (_ MerkleTreeNode, err error) {
|
||||
func (n *InnerNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
|
||||
var err error
|
||||
deposits := math.PowerOf2(depth)
|
||||
if deposits <= depositsToFinalize {
|
||||
return &FinalizedNode{deposits, n.GetRoot()}, nil
|
||||
@@ -286,9 +285,9 @@ type ZeroNode struct {
|
||||
// GetRoot returns the root of the Merkle tree.
|
||||
func (z *ZeroNode) GetRoot() [32]byte {
|
||||
if z.depth == DepositContractDepth {
|
||||
return hash.Hash(append(Zerohashes[z.depth-1][:], Zerohashes[z.depth-1][:]...))
|
||||
return hash.Hash(append(trie.ZeroHashes[z.depth-1][:], trie.ZeroHashes[z.depth-1][:]...))
|
||||
}
|
||||
return Zerohashes[z.depth]
|
||||
return trie.ZeroHashes[z.depth]
|
||||
}
|
||||
|
||||
// IsFull returns wh ether there is space left for deposits.
|
||||
@@ -300,7 +299,7 @@ func (_ *ZeroNode) IsFull() bool {
|
||||
|
||||
// Finalize marks deposits of the Merkle tree as finalized.
|
||||
func (_ *ZeroNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
|
||||
return nil, nil
|
||||
return &ZeroNode{}, nil
|
||||
}
|
||||
|
||||
// GetFinalized returns a list of hashes of all the finalized nodes and the number of deposits.
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
@@ -60,45 +62,54 @@ func Test_fromSnapshotParts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
finalized [][32]byte
|
||||
deposits uint64
|
||||
level uint64
|
||||
want MerkleTreeNode
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
finalized: nil,
|
||||
deposits: 0,
|
||||
level: 0,
|
||||
want: &ZeroNode{},
|
||||
},
|
||||
{
|
||||
name: "single finalized node",
|
||||
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
|
||||
deposits: 1,
|
||||
level: 0,
|
||||
want: &FinalizedNode{
|
||||
depositCount: 1,
|
||||
hash: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple deposits and 1 Finalized",
|
||||
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
|
||||
deposits: 2,
|
||||
level: 4,
|
||||
want: &InnerNode{
|
||||
left: &InnerNode{&InnerNode{&FinalizedNode{depositCount: 2, hash: hexString(t, fmt.Sprintf("%064d", 0))}, &ZeroNode{1}}, &ZeroNode{2}},
|
||||
right: &ZeroNode{3},
|
||||
},
|
||||
name: "multiple deposits and multiple Finalized",
|
||||
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 1)), hexString(t, fmt.Sprintf("%064d", 2))},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tree, err := fromSnapshotParts(tt.finalized, tt.deposits, tt.level)
|
||||
require.NoError(t, err)
|
||||
if got := tree; !reflect.DeepEqual(got, tt.want) {
|
||||
require.DeepEqual(t, tt.want, got)
|
||||
test := NewDepositTree()
|
||||
for _, leaf := range tt.finalized {
|
||||
err := test.pushLeaf(leaf)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := test.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
transformed := make([][]byte, len(tt.finalized))
|
||||
for i := 0; i < len(tt.finalized); i++ {
|
||||
transformed[i] = bytesutil.SafeCopyBytes(tt.finalized[i][:])
|
||||
}
|
||||
generatedTrie, err := trie.GenerateTrieFromItems(transformed, 32)
|
||||
require.NoError(t, err)
|
||||
|
||||
want, err := generatedTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, want, got)
|
||||
|
||||
// Test finalization
|
||||
for i := 0; i < len(tt.finalized); i++ {
|
||||
err = test.Finalize(int64(i), tt.finalized[i], 0)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
sShot, err := test.GetSnapshot()
|
||||
require.NoError(t, err)
|
||||
got, err = sShot.CalculateRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(sShot.finalized))
|
||||
require.Equal(t, want, got)
|
||||
|
||||
// Build from the snapshot once more
|
||||
recovered, err := fromSnapshot(sShot)
|
||||
require.NoError(t, err)
|
||||
got, err = recovered.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -125,7 +136,7 @@ func Test_generateProof(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testCases, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
tree := New()
|
||||
tree := NewDepositTree()
|
||||
for _, c := range testCases[:tt.leaves] {
|
||||
err = tree.pushLeaf(c.DepositDataRoot)
|
||||
require.NoError(t, err)
|
||||
|
||||
93
beacon-chain/cache/depositsnapshot/spec_test.go
vendored
93
beacon-chain/cache/depositsnapshot/spec_test.go
vendored
@@ -1,16 +1,18 @@
|
||||
package depositsnapshot
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bazelbuild/rules_go/go/tools/bazel"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -176,6 +178,9 @@ func readTestCases() ([]testCase, error) {
|
||||
if err != nil {
|
||||
return []testCase{}, err
|
||||
}
|
||||
if len(testCases) == 0 {
|
||||
return nil, errors.New("no test cases found")
|
||||
}
|
||||
return testCases, nil
|
||||
}
|
||||
}
|
||||
@@ -183,11 +188,8 @@ func readTestCases() ([]testCase, error) {
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
tcs, err := readTestCases()
|
||||
_, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
for _, tc := range tcs {
|
||||
t.Log(tc)
|
||||
}
|
||||
}
|
||||
|
||||
func hexStringToByteArray(s string) (b [32]byte, err error) {
|
||||
@@ -222,9 +224,9 @@ func merkleRootFromBranch(leaf [32]byte, branch [][32]byte, index uint64) [32]by
|
||||
for i, l := range branch {
|
||||
ithBit := (index >> i) & 0x1
|
||||
if ithBit == 1 {
|
||||
root = sha256.Sum256(append(l[:], root[:]...))
|
||||
root = hash.Hash(append(l[:], root[:]...))
|
||||
} else {
|
||||
root = sha256.Sum256(append(root[:], l[:]...))
|
||||
root = hash.Hash(append(root[:], l[:]...))
|
||||
}
|
||||
}
|
||||
return root
|
||||
@@ -250,11 +252,11 @@ func cloneFromSnapshot(t *testing.T, snapshot DepositTreeSnapshot, testCases []t
|
||||
err = cp.pushLeaf(c.DepositDataRoot)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return &cp
|
||||
return cp
|
||||
}
|
||||
|
||||
func TestDepositCases(t *testing.T) {
|
||||
tree := New()
|
||||
tree := NewDepositTree()
|
||||
testCases, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
for _, c := range testCases {
|
||||
@@ -263,8 +265,35 @@ func TestDepositCases(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
DepositDataRoot [32]byte
|
||||
}
|
||||
|
||||
func TestRootEquivalence(t *testing.T) {
|
||||
var err error
|
||||
tree := NewDepositTree()
|
||||
testCases, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
|
||||
transformed := make([][]byte, len(testCases[:128]))
|
||||
for i, c := range testCases[:128] {
|
||||
err = tree.pushLeaf(c.DepositDataRoot)
|
||||
require.NoError(t, err)
|
||||
transformed[i] = bytesutil.SafeCopyBytes(c.DepositDataRoot[:])
|
||||
}
|
||||
originalRoot, err := tree.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
generatedTrie, err := trie.GenerateTrieFromItems(transformed, 32)
|
||||
require.NoError(t, err)
|
||||
|
||||
rootA, err := generatedTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rootA, originalRoot)
|
||||
}
|
||||
|
||||
func TestFinalization(t *testing.T) {
|
||||
tree := New()
|
||||
tree := NewDepositTree()
|
||||
testCases, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
for _, c := range testCases[:128] {
|
||||
@@ -273,15 +302,11 @@ func TestFinalization(t *testing.T) {
|
||||
}
|
||||
originalRoot := tree.getRoot()
|
||||
require.DeepEqual(t, testCases[127].Eth1Data.DepositRoot, originalRoot)
|
||||
err = tree.finalize(ð.Eth1Data{
|
||||
DepositRoot: testCases[100].Eth1Data.DepositRoot[:],
|
||||
DepositCount: testCases[100].Eth1Data.DepositCount,
|
||||
BlockHash: testCases[100].Eth1Data.BlockHash[:],
|
||||
}, testCases[100].BlockHeight)
|
||||
err = tree.Finalize(int64(testCases[100].Eth1Data.DepositCount-1), testCases[100].Eth1Data.BlockHash, testCases[100].BlockHeight)
|
||||
require.NoError(t, err)
|
||||
// ensure finalization doesn't change root
|
||||
require.Equal(t, tree.getRoot(), originalRoot)
|
||||
snapshotData, err := tree.getSnapshot()
|
||||
snapshotData, err := tree.GetSnapshot()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, testCases[100].Snapshot.DepositTreeSnapshot, snapshotData)
|
||||
// create a copy of the tree from a snapshot by replaying
|
||||
@@ -290,20 +315,16 @@ func TestFinalization(t *testing.T) {
|
||||
// ensure original and copy have the same root
|
||||
require.Equal(t, tree.getRoot(), cp.getRoot())
|
||||
// finalize original again to check double finalization
|
||||
err = tree.finalize(ð.Eth1Data{
|
||||
DepositRoot: testCases[105].Eth1Data.DepositRoot[:],
|
||||
DepositCount: testCases[105].Eth1Data.DepositCount,
|
||||
BlockHash: testCases[105].Eth1Data.BlockHash[:],
|
||||
}, testCases[105].BlockHeight)
|
||||
err = tree.Finalize(int64(testCases[105].Eth1Data.DepositCount-1), testCases[105].Eth1Data.BlockHash, testCases[105].BlockHeight)
|
||||
require.NoError(t, err)
|
||||
// root should still be the same
|
||||
require.Equal(t, originalRoot, tree.getRoot())
|
||||
// create a copy of the tree by taking a snapshot again
|
||||
snapshotData, err = tree.getSnapshot()
|
||||
snapshotData, err = tree.GetSnapshot()
|
||||
require.NoError(t, err)
|
||||
cp = cloneFromSnapshot(t, snapshotData, testCases[106:128])
|
||||
// create a copy of the tree by replaying ALL deposits from nothing
|
||||
fullTreeCopy := New()
|
||||
fullTreeCopy := NewDepositTree()
|
||||
for _, c := range testCases[:128] {
|
||||
err = fullTreeCopy.pushLeaf(c.DepositDataRoot)
|
||||
require.NoError(t, err)
|
||||
@@ -315,7 +336,7 @@ func TestFinalization(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSnapshotCases(t *testing.T) {
|
||||
tree := New()
|
||||
tree := NewDepositTree()
|
||||
testCases, err := readTestCases()
|
||||
require.NoError(t, err)
|
||||
for _, c := range testCases {
|
||||
@@ -323,33 +344,29 @@ func TestSnapshotCases(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
for _, c := range testCases {
|
||||
err = tree.finalize(ð.Eth1Data{
|
||||
DepositRoot: c.Eth1Data.DepositRoot[:],
|
||||
DepositCount: c.Eth1Data.DepositCount,
|
||||
BlockHash: c.Eth1Data.BlockHash[:],
|
||||
}, c.BlockHeight)
|
||||
err = tree.Finalize(int64(c.Eth1Data.DepositCount-1), c.Eth1Data.BlockHash, c.BlockHeight)
|
||||
require.NoError(t, err)
|
||||
s, err := tree.getSnapshot()
|
||||
s, err := tree.GetSnapshot()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, c.Snapshot.DepositTreeSnapshot, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyTreeSnapshot(t *testing.T) {
|
||||
_, err := New().getSnapshot()
|
||||
require.ErrorContains(t, "empty execution block", err)
|
||||
}
|
||||
|
||||
func TestInvalidSnapshot(t *testing.T) {
|
||||
invalidSnapshot := DepositTreeSnapshot{
|
||||
finalized: nil,
|
||||
depositRoot: Zerohashes[0],
|
||||
depositRoot: trie.ZeroHashes[0],
|
||||
depositCount: 0,
|
||||
executionBlock: executionBlock{
|
||||
Hash: Zerohashes[0],
|
||||
Hash: trie.ZeroHashes[0],
|
||||
Depth: 0,
|
||||
},
|
||||
}
|
||||
_, err := fromSnapshot(invalidSnapshot)
|
||||
require.ErrorContains(t, "snapshot root is invalid", err)
|
||||
}
|
||||
|
||||
func TestEmptyTree(t *testing.T) {
|
||||
tree := NewDepositTree()
|
||||
require.Equal(t, fmt.Sprintf("%x", tree.getRoot()), "d70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e")
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Code generated by gen_zerohashes. DO NOT EDIT.
|
||||
package depositsnapshot
|
||||
|
||||
var Zerohashes = [][32]byte{
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
// f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b
|
||||
{245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75},
|
||||
// db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71
|
||||
{219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236, 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113},
|
||||
// c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c
|
||||
{199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237, 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60},
|
||||
// 536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c
|
||||
{83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213, 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60},
|
||||
// 9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30
|
||||
{158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3, 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48},
|
||||
// d88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1
|
||||
{216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46, 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161},
|
||||
// 87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c
|
||||
{135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6, 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44},
|
||||
// 26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193
|
||||
{38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83, 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147},
|
||||
// 506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1
|
||||
{80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90, 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225},
|
||||
// ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b
|
||||
{255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225, 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43},
|
||||
// 6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220
|
||||
{108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23, 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32},
|
||||
// b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f
|
||||
{183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15, 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95},
|
||||
// df6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e
|
||||
{223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103, 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94},
|
||||
// b58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784
|
||||
{181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117, 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132},
|
||||
// d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb
|
||||
{212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167, 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187},
|
||||
// 8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb
|
||||
{143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235},
|
||||
// 8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab
|
||||
{141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190, 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171},
|
||||
// 95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4
|
||||
{149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84, 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164},
|
||||
// f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f
|
||||
{248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108, 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127},
|
||||
// cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa
|
||||
{205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170},
|
||||
// 8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c
|
||||
{138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138, 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156},
|
||||
// feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167
|
||||
{254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155, 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103},
|
||||
// e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7
|
||||
{231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236, 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215},
|
||||
// 31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0
|
||||
{49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200, 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192},
|
||||
// 21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544
|
||||
{33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67, 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68},
|
||||
// 619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765
|
||||
{97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179, 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101},
|
||||
// 7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4
|
||||
{124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196},
|
||||
// 848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1
|
||||
{132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184, 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225},
|
||||
// 8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636
|
||||
{136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176, 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54},
|
||||
// b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c
|
||||
{181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192, 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92},
|
||||
// 985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7
|
||||
{152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124, 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247},
|
||||
}
|
||||
57
beacon-chain/cache/interfaces.go
vendored
Normal file
57
beacon-chain/cache/interfaces.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// DepositCache combines the interfaces for retrieving and inserting deposit information.
|
||||
type DepositCache interface {
|
||||
DepositFetcher
|
||||
DepositInserter
|
||||
}
|
||||
|
||||
// DepositFetcher defines a struct which can retrieve deposit information from a store.
|
||||
type DepositFetcher interface {
|
||||
AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
|
||||
AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer
|
||||
DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int)
|
||||
DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte)
|
||||
InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte)
|
||||
PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
|
||||
PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer
|
||||
PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64)
|
||||
PruneProofs(ctx context.Context, untilDepositIndex int64) error
|
||||
FinalizedFetcher
|
||||
}
|
||||
|
||||
// DepositInserter defines a struct which can insert deposit information from a store.
|
||||
type DepositInserter interface {
|
||||
InsertDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) error
|
||||
InsertDepositContainers(ctx context.Context, ctrs []*ethpb.DepositContainer)
|
||||
InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64, executionHash common.Hash, executionNumber uint64) error
|
||||
}
|
||||
|
||||
// FinalizedFetcher is a smaller interface defined to be the bare minimum to satisfy “Service”.
|
||||
// It extends the "DepositFetcher" interface with additional methods for fetching finalized deposits.
|
||||
type FinalizedFetcher interface {
|
||||
FinalizedDeposits(ctx context.Context) (FinalizedDeposits, error)
|
||||
NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit
|
||||
}
|
||||
|
||||
// FinalizedDeposits defines a method to access a merkle tree containing deposits and their indexes.
|
||||
type FinalizedDeposits interface {
|
||||
Deposits() MerkleTree
|
||||
MerkleTrieIndex() int64
|
||||
}
|
||||
|
||||
// MerkleTree defines methods for constructing and manipulating a merkle tree.
|
||||
type MerkleTree interface {
|
||||
HashTreeRoot() ([32]byte, error)
|
||||
NumOfItems() int
|
||||
Insert(item []byte, index int) error
|
||||
MerkleProof(index int) ([][]byte, error)
|
||||
}
|
||||
@@ -12,11 +12,20 @@ import (
|
||||
types "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
errBlobSlotMismatch = errors.New("sidecar slot mismatch")
|
||||
errBlobParentMismatch = errors.New("sidecar parent root mismatch")
|
||||
errBlobRootMismatch = errors.New("sidecar root mismatch")
|
||||
errBlobProposerMismatch = errors.New("sidecar proposer index mismatch")
|
||||
errBlobSidecarLimit = errors.New("sidecar exceeds maximum number of blobs")
|
||||
errEmptySidecar = errors.New("nil or empty blob sidecars")
|
||||
errNewerBlobExists = errors.New("Will not overwrite newer blobs in db")
|
||||
)
|
||||
|
||||
// A blob rotating key is represented as bytes(slot_to_rotating_buffer(blob.slot)) ++ bytes(blob.slot) ++ blob.block_root
|
||||
type blobRotatingKey []byte
|
||||
|
||||
@@ -45,94 +54,119 @@ func (rk blobRotatingKey) BlockRoot() []byte {
|
||||
//
|
||||
// 3. Begin the save algorithm: If the incoming blob has a slot bigger than the saved slot at the spot
|
||||
// in the rotating keys buffer, we overwrite all elements for that slot. Otherwise, we merge the blob with an existing one.
|
||||
// Trying to replace a newer blob with an older one is an error.
|
||||
func (s *Store) SaveBlobSidecar(ctx context.Context, scs []*ethpb.BlobSidecar) error {
|
||||
if len(scs) == 0 {
|
||||
return errEmptySidecar
|
||||
}
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlobSidecar")
|
||||
defer span.End()
|
||||
|
||||
sortSideCars(scs)
|
||||
if err := s.verifySideCars(scs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
first := scs[0]
|
||||
newKey := blobSidecarKey(first)
|
||||
prefix := newKey.BufferPrefix()
|
||||
var prune []blobRotatingKey
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
encodedBlobSidecar, err := encode(ctx, ðpb.BlobSidecars{Sidecars: scs})
|
||||
var existing []byte
|
||||
sc := ðpb.BlobSidecars{}
|
||||
bkt := tx.Bucket(blobsBucket)
|
||||
c := bkt.Cursor()
|
||||
for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() {
|
||||
key := blobRotatingKey(k)
|
||||
ks := key.Slot()
|
||||
if ks < first.Slot {
|
||||
// Mark older blobs at the same position of the ring buffer for deletion.
|
||||
prune = append(prune, key)
|
||||
continue
|
||||
}
|
||||
if ks > first.Slot {
|
||||
// We shouldn't be overwriting newer blobs with older blobs. Something is wrong.
|
||||
return errNewerBlobExists
|
||||
}
|
||||
// The slot isn't older or newer, so it must be equal.
|
||||
// If the roots match, then we want to merge the new sidecars with the existing data.
|
||||
if bytes.Equal(first.BlockRoot, key.BlockRoot()) {
|
||||
existing = v
|
||||
if err := decode(ctx, v, sc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If the slot is equal but the roots don't match, leave the existing key alone and allow the sidecar
|
||||
// to be written to the new key with the same prefix. In this case sc will be empty, so it will just
|
||||
// contain the incoming sidecars when we write it.
|
||||
}
|
||||
sc.Sidecars = append(sc.Sidecars, scs...)
|
||||
sortSidecars(sc.Sidecars)
|
||||
var err error
|
||||
sc.Sidecars, err = validUniqueSidecars(sc.Sidecars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bkt := tx.Bucket(blobsBucket)
|
||||
c := bkt.Cursor()
|
||||
newKey := blobSidecarKey(scs[0])
|
||||
rotatingBufferPrefix := newKey.BufferPrefix()
|
||||
var replacingKey blobRotatingKey
|
||||
for k, _ := c.Seek(rotatingBufferPrefix); bytes.HasPrefix(k, rotatingBufferPrefix); k, _ = c.Next() {
|
||||
if len(k) != 0 {
|
||||
replacingKey = k
|
||||
break
|
||||
encoded, err := encode(ctx, sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// don't write if the merged result is the same as before
|
||||
if len(existing) == len(encoded) && bytes.Equal(existing, encoded) {
|
||||
return nil
|
||||
}
|
||||
// Only prune if we're actually going through with the update.
|
||||
for _, k := range prune {
|
||||
if err := bkt.Delete(k); err != nil {
|
||||
// note: attempting to delete a key that does not exist should not return an error.
|
||||
log.WithError(err).Warnf("Could not delete blob key %#x.", k)
|
||||
}
|
||||
}
|
||||
// If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply
|
||||
// store the blob by key and exit early.
|
||||
if len(replacingKey) != 0 {
|
||||
oldSlot := replacingKey.Slot()
|
||||
oldEpoch := slots.ToEpoch(oldSlot)
|
||||
// The blob we are replacing is too old, so we delete it.
|
||||
if slots.ToEpoch(scs[0].Slot) >= oldEpoch.Add(uint64(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)) {
|
||||
if err := bkt.Delete(replacingKey); err != nil {
|
||||
log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we need to merge the new blob with the old blob.
|
||||
enc := bkt.Get(replacingKey)
|
||||
sc := ðpb.BlobSidecars{}
|
||||
if err := decode(ctx, enc, sc); err != nil {
|
||||
return err
|
||||
}
|
||||
sc.Sidecars = append(sc.Sidecars, scs...)
|
||||
sortSideCars(sc.Sidecars)
|
||||
encodedBlobSidecar, err = encode(ctx, ðpb.BlobSidecars{Sidecars: sc.Sidecars})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return bkt.Put(newKey, encodedBlobSidecar)
|
||||
return bkt.Put(newKey, encoded)
|
||||
})
|
||||
}
|
||||
|
||||
// verifySideCars ensures that all sidecars have the same slot, parent root, block root, and proposer index, and no more than MAX_BLOB_EPOCHS.
|
||||
func (s *Store) verifySideCars(scs []*ethpb.BlobSidecar) error {
|
||||
// validUniqueSidecars ensures that all sidecars have the same slot, parent root, block root, and proposer index, and no more than MAX_BLOB_EPOCHS.
|
||||
func validUniqueSidecars(scs []*ethpb.BlobSidecar) ([]*ethpb.BlobSidecar, error) {
|
||||
if len(scs) == 0 {
|
||||
return errors.New("nil or empty blob sidecars")
|
||||
}
|
||||
if uint64(len(scs)) > fieldparams.MaxBlobsPerBlock {
|
||||
return fmt.Errorf("too many sidecars: %d > %d", len(scs), fieldparams.MaxBlobsPerBlock)
|
||||
return nil, errEmptySidecar
|
||||
}
|
||||
|
||||
sl := scs[0].Slot
|
||||
pr := scs[0].BlockParentRoot
|
||||
r := scs[0].BlockRoot
|
||||
p := scs[0].ProposerIndex
|
||||
|
||||
for _, sc := range scs {
|
||||
if sc.Slot != sl {
|
||||
return fmt.Errorf("sidecar slot mismatch: %d != %d", sc.Slot, sl)
|
||||
}
|
||||
if !bytes.Equal(sc.BlockParentRoot, pr) {
|
||||
return fmt.Errorf("sidecar parent root mismatch: %x != %x", sc.BlockParentRoot, pr)
|
||||
}
|
||||
if !bytes.Equal(sc.BlockRoot, r) {
|
||||
return fmt.Errorf("sidecar root mismatch: %x != %x", sc.BlockRoot, r)
|
||||
}
|
||||
if sc.ProposerIndex != p {
|
||||
return fmt.Errorf("sidecar proposer index mismatch: %d != %d", sc.ProposerIndex, p)
|
||||
}
|
||||
// If there's only 1 sidecar, we've got nothing to compare.
|
||||
if len(scs) == 1 {
|
||||
return scs, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
prev := scs[0]
|
||||
didx := 1
|
||||
for i := 1; i < len(scs); i++ {
|
||||
sc := scs[i]
|
||||
if sc.Slot != prev.Slot {
|
||||
return nil, errors.Wrapf(errBlobSlotMismatch, "%d != %d", sc.Slot, prev.Slot)
|
||||
}
|
||||
if !bytes.Equal(sc.BlockParentRoot, prev.BlockParentRoot) {
|
||||
return nil, errors.Wrapf(errBlobParentMismatch, "%x != %x", sc.BlockParentRoot, prev.BlockParentRoot)
|
||||
}
|
||||
if !bytes.Equal(sc.BlockRoot, prev.BlockRoot) {
|
||||
return nil, errors.Wrapf(errBlobRootMismatch, "%x != %x", sc.BlockRoot, prev.BlockRoot)
|
||||
}
|
||||
if sc.ProposerIndex != prev.ProposerIndex {
|
||||
return nil, errors.Wrapf(errBlobProposerMismatch, "%d != %d", sc.ProposerIndex, prev.ProposerIndex)
|
||||
}
|
||||
// skip duplicate
|
||||
if sc.Index == prev.Index {
|
||||
continue
|
||||
}
|
||||
if didx != i {
|
||||
scs[didx] = scs[i]
|
||||
}
|
||||
prev = scs[i]
|
||||
didx += 1
|
||||
}
|
||||
|
||||
if didx > fieldparams.MaxBlobsPerBlock {
|
||||
return nil, errors.Wrapf(errBlobSidecarLimit, "%d > %d", didx, fieldparams.MaxBlobsPerBlock)
|
||||
}
|
||||
return scs[0:didx], nil
|
||||
}
|
||||
|
||||
// sortSideCars sorts the sidecars by their index.
|
||||
func sortSideCars(scs []*ethpb.BlobSidecar) {
|
||||
// sortSidecars sorts the sidecars by their index.
|
||||
func sortSidecars(scs []*ethpb.BlobSidecar) {
|
||||
sort.Slice(scs, func(i, j int) bool {
|
||||
return scs[i].Index < scs[j].Index
|
||||
})
|
||||
|
||||
@@ -294,6 +294,24 @@ func TestStore_BlobSidecars(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, equalBlobSlices(scs, got))
|
||||
})
|
||||
t.Run("save equivocating blobs", func(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
scs := generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock/2)
|
||||
eScs := generateEquivocatingBlobSidecars(t, fieldparams.MaxBlobsPerBlock/2)
|
||||
|
||||
for i, sc := range scs {
|
||||
require.NoError(t, db.SaveBlobSidecar(ctx, []*ethpb.BlobSidecar{sc}))
|
||||
require.NoError(t, db.SaveBlobSidecar(ctx, []*ethpb.BlobSidecar{eScs[i]}))
|
||||
}
|
||||
|
||||
got, err := db.BlobSidecarsByRoot(ctx, bytesutil.ToBytes32(scs[0].BlockRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, equalBlobSlices(scs, got))
|
||||
|
||||
got, err = db.BlobSidecarsByRoot(ctx, bytesutil.ToBytes32(eScs[0].BlockRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, equalBlobSlices(eScs, got))
|
||||
})
|
||||
}
|
||||
|
||||
func generateBlobSidecars(t *testing.T, n uint64) []*ethpb.BlobSidecar {
|
||||
@@ -327,26 +345,56 @@ func generateBlobSidecar(t *testing.T, index uint64) *ethpb.BlobSidecar {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_verifySideCars(t *testing.T) {
|
||||
s := setupDB(t)
|
||||
func generateEquivocatingBlobSidecars(t *testing.T, n uint64) []*ethpb.BlobSidecar {
|
||||
blobSidecars := make([]*ethpb.BlobSidecar, n)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
blobSidecars[i] = generateEquivocatingBlobSidecar(t, i)
|
||||
}
|
||||
return blobSidecars
|
||||
}
|
||||
|
||||
func generateEquivocatingBlobSidecar(t *testing.T, index uint64) *ethpb.BlobSidecar {
|
||||
blob := make([]byte, 131072)
|
||||
_, err := rand.Read(blob)
|
||||
require.NoError(t, err)
|
||||
kzgCommitment := make([]byte, 48)
|
||||
_, err = rand.Read(kzgCommitment)
|
||||
require.NoError(t, err)
|
||||
kzgProof := make([]byte, 48)
|
||||
_, err = rand.Read(kzgProof)
|
||||
require.NoError(t, err)
|
||||
|
||||
return ðpb.BlobSidecar{
|
||||
BlockRoot: bytesutil.PadTo([]byte{'c'}, 32),
|
||||
Index: index,
|
||||
Slot: 100,
|
||||
BlockParentRoot: bytesutil.PadTo([]byte{'b'}, 32),
|
||||
ProposerIndex: 102,
|
||||
Blob: blob,
|
||||
KzgCommitment: kzgCommitment,
|
||||
KzgProof: kzgProof,
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validUniqueSidecars_validation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
scs []*ethpb.BlobSidecar
|
||||
error string
|
||||
name string
|
||||
scs []*ethpb.BlobSidecar
|
||||
err error
|
||||
}{
|
||||
{name: "empty", scs: []*ethpb.BlobSidecar{}, error: "nil or empty blob sidecars"},
|
||||
{name: "too many sidecars", scs: generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock+1), error: "too many sidecars: 7 > 6"},
|
||||
{name: "invalid slot", scs: []*ethpb.BlobSidecar{{Slot: 1}, {Slot: 2}}, error: "sidecar slot mismatch: 2 != 1"},
|
||||
{name: "invalid proposer index", scs: []*ethpb.BlobSidecar{{ProposerIndex: 1}, {ProposerIndex: 2}}, error: "sidecar proposer index mismatch: 2 != 1"},
|
||||
{name: "invalid root", scs: []*ethpb.BlobSidecar{{BlockRoot: []byte{1}}, {BlockRoot: []byte{2}}}, error: "sidecar root mismatch: 02 != 01"},
|
||||
{name: "invalid parent root", scs: []*ethpb.BlobSidecar{{BlockParentRoot: []byte{1}}, {BlockParentRoot: []byte{2}}}, error: "sidecar parent root mismatch: 02 != 01"},
|
||||
{name: "happy path", scs: []*ethpb.BlobSidecar{{Index: 0}, {Index: 1}}, error: ""},
|
||||
{name: "empty", scs: []*ethpb.BlobSidecar{}, err: errEmptySidecar},
|
||||
{name: "too many sidecars", scs: generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock+1), err: errBlobSidecarLimit},
|
||||
{name: "invalid slot", scs: []*ethpb.BlobSidecar{{Slot: 1}, {Slot: 2}}, err: errBlobSlotMismatch},
|
||||
{name: "invalid proposer index", scs: []*ethpb.BlobSidecar{{ProposerIndex: 1}, {ProposerIndex: 2}}, err: errBlobProposerMismatch},
|
||||
{name: "invalid root", scs: []*ethpb.BlobSidecar{{BlockRoot: []byte{1}}, {BlockRoot: []byte{2}}}, err: errBlobRootMismatch},
|
||||
{name: "invalid parent root", scs: []*ethpb.BlobSidecar{{BlockParentRoot: []byte{1}}, {BlockParentRoot: []byte{2}}}, err: errBlobParentMismatch},
|
||||
{name: "happy path", scs: []*ethpb.BlobSidecar{{Index: 0}, {Index: 1}}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := s.verifySideCars(tt.scs)
|
||||
if tt.error != "" {
|
||||
require.Equal(t, tt.error, err.Error())
|
||||
_, err := validUniqueSidecars(tt.scs)
|
||||
if tt.err != nil {
|
||||
require.ErrorIs(t, err, tt.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -354,6 +402,62 @@ func TestStore_verifySideCars(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validUniqueSidecars_dedup(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
scs []*ethpb.BlobSidecar
|
||||
expected []*ethpb.BlobSidecar
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "duplicate sidecar",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 1}},
|
||||
expected: []*ethpb.BlobSidecar{{Index: 1}},
|
||||
},
|
||||
{
|
||||
name: "single sidecar",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}},
|
||||
expected: []*ethpb.BlobSidecar{{Index: 1}},
|
||||
},
|
||||
{
|
||||
name: "multiple duplicates",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 2}, {Index: 3}, {Index: 3}},
|
||||
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}},
|
||||
},
|
||||
{
|
||||
name: "ok number after de-dupe, > 6 before",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 2}, {Index: 2}, {Index: 2}, {Index: 3}, {Index: 3}},
|
||||
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}},
|
||||
},
|
||||
{
|
||||
name: "max unique, no dupes",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}},
|
||||
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}},
|
||||
},
|
||||
{
|
||||
name: "too many unique",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}, {Index: 7}},
|
||||
err: errBlobSidecarLimit,
|
||||
},
|
||||
{
|
||||
name: "too many unique with dupes",
|
||||
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 1}, {Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}, {Index: 7}},
|
||||
err: errBlobSidecarLimit,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
u, err := validUniqueSidecars(c.scs)
|
||||
if c.err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, len(c.expected), len(u))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_sortSidecars(t *testing.T) {
|
||||
scs := []*ethpb.BlobSidecar{
|
||||
{Index: 6},
|
||||
@@ -364,7 +468,7 @@ func TestStore_sortSidecars(t *testing.T) {
|
||||
{Index: 5},
|
||||
{},
|
||||
}
|
||||
sortSideCars(scs)
|
||||
sortSidecars(scs)
|
||||
for i := 0; i < len(scs)-1; i++ {
|
||||
require.Equal(t, uint64(i), scs[i].Index)
|
||||
}
|
||||
|
||||
@@ -111,10 +111,10 @@ var blockTests = []struct {
|
||||
name: "deneb blind",
|
||||
newBlock: func(slot primitives.Slot, root []byte) (interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
b := util.NewBlindedBeaconBlockDeneb()
|
||||
b.Block.Slot = slot
|
||||
b.Message.Slot = slot
|
||||
if root != nil {
|
||||
b.Block.ParentRoot = root
|
||||
b.Block.Body.BlobKzgCommitments = [][]byte{
|
||||
b.Message.ParentRoot = root
|
||||
b.Message.Body.BlobKzgCommitments = [][]byte{
|
||||
bytesutil.PadTo([]byte{0x05}, 48),
|
||||
bytesutil.PadTo([]byte{0x06}, 48),
|
||||
bytesutil.PadTo([]byte{0x07}, 48),
|
||||
|
||||
@@ -9,7 +9,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/deterministic-genesis",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
deps = [
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/execution:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
var _ runtime.Service = (*Service)(nil)
|
||||
var _ depositcache.DepositFetcher = (*Service)(nil)
|
||||
var _ cache.FinalizedFetcher = (*Service)(nil)
|
||||
var _ execution.ChainStartFetcher = (*Service)(nil)
|
||||
|
||||
// Service spins up an client interoperability service that handles responsibilities such
|
||||
@@ -34,12 +34,42 @@ type Service struct {
|
||||
chainStartDeposits []*ethpb.Deposit
|
||||
}
|
||||
|
||||
// All of these methods are stubs as they are not used by a node running with deterministic-genesis.
|
||||
|
||||
func (s *Service) AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer {
|
||||
log.Errorf("AllDepositContainers should not be called")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) {
|
||||
log.Errorf("InsertPendingDeposit should not be called")
|
||||
}
|
||||
|
||||
func (s *Service) PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
|
||||
log.Errorf("PendingDeposits should not be called")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer {
|
||||
log.Errorf("PendingContainers should not be called")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64) {
|
||||
log.Errorf("PrunePendingDeposits should not be called")
|
||||
}
|
||||
|
||||
func (s *Service) PruneProofs(ctx context.Context, untilDepositIndex int64) error {
|
||||
log.Errorf("PruneProofs should not be called")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config options for the interop service.
|
||||
type Config struct {
|
||||
GenesisTime uint64
|
||||
NumValidators uint64
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
DepositCache *depositcache.DepositCache
|
||||
DepositCache cache.DepositCache
|
||||
GenesisPath string
|
||||
}
|
||||
|
||||
@@ -148,8 +178,8 @@ func (_ *Service) DepositsNumberAndRootAtHeight(_ context.Context, _ *big.Int) (
|
||||
}
|
||||
|
||||
// FinalizedDeposits mocks out the deposit cache functionality for interop.
|
||||
func (_ *Service) FinalizedDeposits(_ context.Context) *depositcache.FinalizedDeposits {
|
||||
return nil
|
||||
func (_ *Service) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NonFinalizedDeposits mocks out the deposit cache functionality for interop.
|
||||
|
||||
@@ -25,7 +25,8 @@ go_library(
|
||||
"//testing/spectest:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositsnapshot:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
|
||||
@@ -13,13 +13,16 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
coreState "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/types"
|
||||
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
@@ -141,7 +144,6 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to determine hashed value of deposit")
|
||||
}
|
||||
|
||||
// Defensive check to validate incoming index.
|
||||
if s.depositTrie.NumOfItems() != int(index) {
|
||||
return errors.Errorf("invalid deposit index received: wanted %d but got %d", s.depositTrie.NumOfItems(), index)
|
||||
@@ -149,7 +151,6 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
|
||||
if err = s.depositTrie.Insert(depositHash[:], int(index)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deposit := ðpb.Deposit{
|
||||
Data: depositData,
|
||||
}
|
||||
@@ -221,6 +222,18 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
|
||||
"merkleTreeIndex": index,
|
||||
}).Info("Invalid deposit registered in deposit contract")
|
||||
}
|
||||
if features.Get().EnableEIP4881 {
|
||||
// We finalize the trie here so that old deposits are not kept around, as they make
|
||||
// deposit tree htr computation expensive.
|
||||
dTrie, ok := s.depositTrie.(*depositsnapshot.DepositTree)
|
||||
if !ok {
|
||||
return errors.Errorf("wrong trie type initialized: %T", dTrie)
|
||||
}
|
||||
if err := dTrie.Finalize(index, depositLog.BlockHash, depositLog.BlockNumber); err != nil {
|
||||
log.WithError(err).Error("Could not finalize trie")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -337,7 +350,7 @@ func (s *Service) processPastLogs(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
if fState != nil && !fState.IsNil() && fState.Eth1DepositIndex() > 0 {
|
||||
s.cfg.depositCache.PrunePendingDeposits(ctx, int64(fState.Eth1DepositIndex())) // lint:ignore uintcast -- Deposit index should not exceed int64 in your lifetime.
|
||||
s.cfg.depositCache.PrunePendingDeposits(ctx, int64(fState.Eth1DepositIndex())) // lint:ignore uintcast -- deposit index should not exceed int64 in your lifetime.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -559,8 +572,27 @@ func (s *Service) savePowchainData(ctx context.Context) error {
|
||||
CurrentEth1Data: s.latestEth1Data,
|
||||
ChainstartData: s.chainStartData,
|
||||
BeaconState: pbState, // I promise not to mutate it!
|
||||
Trie: s.depositTrie.ToProto(),
|
||||
DepositContainers: s.cfg.depositCache.AllDepositContainers(ctx),
|
||||
}
|
||||
if features.Get().EnableEIP4881 {
|
||||
fd, err := s.cfg.depositCache.FinalizedDeposits(ctx)
|
||||
if err != nil {
|
||||
return errors.Errorf("could not get finalized deposit tree: %v", err)
|
||||
}
|
||||
tree, ok := fd.Deposits().(*depositsnapshot.DepositTree)
|
||||
if !ok {
|
||||
return errors.New("deposit tree was not EIP4881 DepositTree")
|
||||
}
|
||||
eth1Data.DepositSnapshot, err = tree.ToProto()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
tree, ok := s.depositTrie.(*trie.SparseMerkleTrie)
|
||||
if !ok {
|
||||
return errors.New("deposit tree was not SparseMerkleTrie")
|
||||
}
|
||||
eth1Data.Trie = tree.ToProto()
|
||||
}
|
||||
return s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package execution
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
@@ -62,7 +62,7 @@ func WithDatabase(database db.HeadAccessDatabase) Option {
|
||||
}
|
||||
|
||||
// WithDepositCache for caching deposits.
|
||||
func WithDepositCache(cache *depositcache.DepositCache) Option {
|
||||
func WithDepositCache(cache cache.DepositCache) Option {
|
||||
return func(s *Service) error {
|
||||
s.cfg.depositCache = cache
|
||||
return nil
|
||||
|
||||
@@ -20,7 +20,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
|
||||
@@ -119,7 +121,7 @@ func (RPCClientEmpty) CallContext(context.Context, interface{}, string, ...inter
|
||||
type config struct {
|
||||
depositContractAddr common.Address
|
||||
beaconDB db.HeadAccessDatabase
|
||||
depositCache *depositcache.DepositCache
|
||||
depositCache cache.DepositCache
|
||||
stateNotifier statefeed.Notifier
|
||||
stateGen *stategen.State
|
||||
eth1HeaderReqLimit uint64
|
||||
@@ -149,7 +151,7 @@ type Service struct {
|
||||
headerCache *headerCache // cache to store block hash/block height.
|
||||
latestEth1Data *ethpb.LatestETH1Data
|
||||
depositContractCaller *contracts.DepositContractCaller
|
||||
depositTrie *trie.SparseMerkleTrie
|
||||
depositTrie cache.MerkleTree
|
||||
chainStartData *ethpb.ChainStartData
|
||||
lastReceivedMerkleIndex int64 // Keeps track of the last received index to prevent log spam.
|
||||
runError error
|
||||
@@ -160,10 +162,15 @@ type Service struct {
|
||||
func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
_ = cancel // govet fix for lost cancel. Cancel is handled in service.Stop()
|
||||
depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, errors.Wrap(err, "could not set up deposit trie")
|
||||
var depositTrie cache.MerkleTree
|
||||
var err error
|
||||
if features.Get().EnableEIP4881 {
|
||||
depositTrie = depositsnapshot.NewDepositTree()
|
||||
} else {
|
||||
depositTrie, err = trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not set up deposit trie")
|
||||
}
|
||||
}
|
||||
genState, err := transition.EmptyGenesisState()
|
||||
if err != nil {
|
||||
@@ -209,7 +216,6 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to retrieve eth1 data")
|
||||
}
|
||||
|
||||
if err := s.initializeEth1Data(ctx, eth1Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -370,7 +376,8 @@ func (s *Service) initDepositCaches(ctx context.Context, ctrs []*ethpb.DepositCo
|
||||
// to be included (rather than the last one to be processed). This was most likely
|
||||
// done as the state cannot represent signed integers.
|
||||
actualIndex := int64(currIndex) - 1 // lint:ignore uintcast -- deposit index will not exceed int64 in your lifetime.
|
||||
if err = s.cfg.depositCache.InsertFinalizedDeposits(ctx, actualIndex); err != nil {
|
||||
if err = s.cfg.depositCache.InsertFinalizedDeposits(ctx, actualIndex, common.Hash(fState.Eth1Data().BlockHash),
|
||||
0 /* Setting a zero value as we have no access to block height */); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -742,7 +749,17 @@ func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *ethpb.ET
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
s.depositTrie, err = trie.CreateTrieFromProto(eth1DataInDB.Trie)
|
||||
if features.Get().EnableEIP4881 {
|
||||
if eth1DataInDB.DepositSnapshot != nil {
|
||||
s.depositTrie, err = depositsnapshot.DepositTreeFromSnapshotProto(eth1DataInDB.DepositSnapshot)
|
||||
} else {
|
||||
if err := s.migrateOldDepositTree(eth1DataInDB); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.depositTrie, err = trie.CreateTrieFromProto(eth1DataInDB.Trie)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -754,6 +771,24 @@ func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *ethpb.ET
|
||||
}
|
||||
}
|
||||
s.latestEth1Data = eth1DataInDB.CurrentEth1Data
|
||||
if features.Get().EnableEIP4881 {
|
||||
ctrs := eth1DataInDB.DepositContainers
|
||||
// Look at previously finalized index, as we are building off a finalized
|
||||
// snapshot rather than the full trie.
|
||||
lastFinalizedIndex := int64(s.depositTrie.NumOfItems() - 1)
|
||||
// Correctly initialize missing deposits into active trie.
|
||||
for _, c := range ctrs {
|
||||
if c.Index > lastFinalizedIndex {
|
||||
depRoot, err := c.Deposit.Data.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.depositTrie.Insert(depRoot[:], int(c.Index)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
numOfItems := s.depositTrie.NumOfItems()
|
||||
s.lastReceivedMerkleIndex = int64(numOfItems - 1)
|
||||
if err := s.initDepositCaches(ctx, eth1DataInDB.DepositContainers); err != nil {
|
||||
@@ -816,9 +851,24 @@ func (s *Service) ensureValidPowchainData(ctx context.Context) error {
|
||||
CurrentEth1Data: s.latestEth1Data,
|
||||
ChainstartData: s.chainStartData,
|
||||
BeaconState: pbState,
|
||||
Trie: s.depositTrie.ToProto(),
|
||||
DepositContainers: s.cfg.depositCache.AllDepositContainers(ctx),
|
||||
}
|
||||
if features.Get().EnableEIP4881 {
|
||||
trie, ok := s.depositTrie.(*depositsnapshot.DepositTree)
|
||||
if !ok {
|
||||
return errors.New("deposit trie was not EIP4881 DepositTree")
|
||||
}
|
||||
eth1Data.DepositSnapshot, err = trie.ToProto()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
trie, ok := s.depositTrie.(*trie.SparseMerkleTrie)
|
||||
if !ok {
|
||||
return errors.New("deposit trie was not SparseMerkleTrie")
|
||||
}
|
||||
eth1Data.Trie = trie.ToProto()
|
||||
}
|
||||
return s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data)
|
||||
}
|
||||
return nil
|
||||
@@ -836,3 +886,29 @@ func dedupEndpoints(endpoints []string) []string {
|
||||
}
|
||||
return newEndpoints
|
||||
}
|
||||
|
||||
func (s *Service) migrateOldDepositTree(eth1DataInDB *ethpb.ETH1ChainData) error {
|
||||
oldDepositTrie, err := trie.CreateTrieFromProto(eth1DataInDB.Trie)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newDepositTrie := depositsnapshot.NewDepositTree()
|
||||
for i, item := range oldDepositTrie.Items() {
|
||||
if err = newDepositTrie.Insert(item, i); err != nil {
|
||||
return errors.Wrapf(err, "could not insert item at index %d into deposit snapshot tree", i)
|
||||
}
|
||||
}
|
||||
newDepositRoot, err := newDepositTrie.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depositRoot, err := oldDepositTrie.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newDepositRoot != depositRoot {
|
||||
return errors.Wrapf(err, "mismatched deposit roots, old %#x != new %#x", depositRoot, newDepositRoot)
|
||||
}
|
||||
s.depositTrie = newDepositTrie
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
|
||||
"github.com/prysmaticlabs/prysm/v4/contracts/deposit/mock"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
@@ -433,8 +434,9 @@ func TestInitDepositCacheWithFinalization_OK(t *testing.T) {
|
||||
|
||||
s.chainStartData.Chainstarted = true
|
||||
require.NoError(t, s.initDepositCaches(context.Background(), ctrs))
|
||||
fDeposits := s.cfg.depositCache.FinalizedDeposits(ctx)
|
||||
deps := s.cfg.depositCache.NonFinalizedDeposits(context.Background(), fDeposits.MerkleTrieIndex, nil)
|
||||
fDeposits, err := s.cfg.depositCache.FinalizedDeposits(ctx)
|
||||
require.NoError(t, err)
|
||||
deps := s.cfg.depositCache.NonFinalizedDeposits(context.Background(), fDeposits.MerkleTrieIndex(), nil)
|
||||
assert.Equal(t, 0, len(deps))
|
||||
}
|
||||
|
||||
@@ -801,3 +803,50 @@ func (s *slowRPCClient) BatchCall(b []rpc.BatchElem) error {
|
||||
func (s *slowRPCClient) CallContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestService_migrateOldDepositTree(t *testing.T) {
|
||||
beaconDB := dbutil.SetupDB(t)
|
||||
cache, err := depositcache.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
srv, endpoint, err := mockExecution.SetupRPCServer()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
srv.Stop()
|
||||
})
|
||||
s, err := NewService(context.Background(),
|
||||
WithHttpEndpoint(endpoint),
|
||||
WithDatabase(beaconDB),
|
||||
WithDepositCache(cache),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
eth1Data := ðpb.ETH1ChainData{
|
||||
BeaconState: ðpb.BeaconState{
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositCount: 800,
|
||||
},
|
||||
},
|
||||
CurrentEth1Data: ðpb.LatestETH1Data{
|
||||
BlockHeight: 100,
|
||||
},
|
||||
}
|
||||
|
||||
totalDeposits := 1000
|
||||
input := bytesutil.ToBytes32([]byte("foo"))
|
||||
dt, err := trie.NewTrie(32)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < totalDeposits; i++ {
|
||||
err := dt.Insert(input[:], i)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
eth1Data.Trie = dt.ToProto()
|
||||
|
||||
err = s.migrateOldDepositTree(eth1Data)
|
||||
require.NoError(t, err)
|
||||
oldDepositTreeRoot, err := dt.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
newDepositTreeRoot, err := s.depositTrie.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, oldDepositTreeRoot, newDepositTreeRoot)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ go_library(
|
||||
"//beacon-chain/builder:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/cache/depositsnapshot:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/kv:go_default_library",
|
||||
"//beacon-chain/db/slasherkv:go_default_library",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/builder"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/kv"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/slasherkv"
|
||||
@@ -94,7 +95,7 @@ type BeaconNode struct {
|
||||
slashingsPool slashings.PoolManager
|
||||
syncCommitteePool synccommittee.Pool
|
||||
blsToExecPool blstoexec.PoolManager
|
||||
depositCache *depositcache.DepositCache
|
||||
depositCache cache.DepositCache
|
||||
proposerIdsCache *cache.ProposerPayloadIDsCache
|
||||
stateFeed *event.Feed
|
||||
blockFeed *event.Feed
|
||||
@@ -406,10 +407,16 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
|
||||
b.db = d
|
||||
|
||||
depositCache, err := depositcache.New()
|
||||
var depositCache cache.DepositCache
|
||||
if features.Get().EnableEIP4881 {
|
||||
depositCache, err = depositsnapshot.New()
|
||||
} else {
|
||||
depositCache, err = depositcache.New()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create deposit cache")
|
||||
}
|
||||
|
||||
b.depositCache = depositCache
|
||||
|
||||
if b.GenesisInitializer != nil {
|
||||
@@ -781,7 +788,7 @@ func (b *BeaconNode) registerRPCService(router *mux.Router) error {
|
||||
}
|
||||
|
||||
genesisValidators := b.cliCtx.Uint64(flags.InteropNumValidatorsFlag.Name)
|
||||
var depositFetcher depositcache.DepositFetcher
|
||||
var depositFetcher cache.DepositFetcher
|
||||
var chainStartFetcher execution.ChainStartFetcher
|
||||
if genesisValidators > 0 {
|
||||
var interopService *interopcoldstart.Service
|
||||
|
||||
@@ -477,8 +477,10 @@ func TestKV_Aggregated_HasAggregatedAttestation(t *testing.T) {
|
||||
|
||||
// Same test for block attestations
|
||||
cache = NewAttCaches()
|
||||
assert.NoError(t, cache.SaveBlockAttestations(tt.existing))
|
||||
|
||||
for _, att := range tt.existing {
|
||||
require.NoError(t, cache.SaveBlockAttestation(att))
|
||||
}
|
||||
result, err = cache.HasAggregatedAttestation(tt.input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, result)
|
||||
|
||||
@@ -36,17 +36,6 @@ func (c *AttCaches) SaveBlockAttestation(att *ethpb.Attestation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveBlockAttestations saves a list of block attestations in cache.
|
||||
func (c *AttCaches) SaveBlockAttestations(atts []*ethpb.Attestation) error {
|
||||
for _, att := range atts {
|
||||
if err := c.SaveBlockAttestation(att); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockAttestations returns the block attestations in cache.
|
||||
func (c *AttCaches) BlockAttestations() []*ethpb.Attestation {
|
||||
atts := make([]*ethpb.Attestation, 0)
|
||||
|
||||
@@ -32,7 +32,6 @@ type Pool interface {
|
||||
UnaggregatedAttestationCount() int
|
||||
// For attestations that were included in the block.
|
||||
SaveBlockAttestation(att *ethpb.Attestation) error
|
||||
SaveBlockAttestations(atts []*ethpb.Attestation) error
|
||||
BlockAttestations() []*ethpb.Attestation
|
||||
DeleteBlockAttestation(att *ethpb.Attestation) error
|
||||
// For attestations to be passed to fork choice.
|
||||
|
||||
@@ -94,7 +94,9 @@ func TestBatchAttestations_Multiple(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, s.cfg.Pool.SaveUnaggregatedAttestations(unaggregatedAtts))
|
||||
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(aggregatedAtts))
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(blockAtts))
|
||||
for _, att := range blockAtts {
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
|
||||
}
|
||||
require.NoError(t, s.batchForkChoiceAtts(context.Background()))
|
||||
|
||||
wanted, err := attaggregation.Aggregate([]*ethpb.Attestation{aggregatedAtts[0], blockAtts[0]})
|
||||
@@ -148,7 +150,10 @@ func TestBatchAttestations_Single(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, s.cfg.Pool.SaveUnaggregatedAttestations(unaggregatedAtts))
|
||||
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(aggregatedAtts))
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(blockAtts))
|
||||
|
||||
for _, att := range blockAtts {
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
|
||||
}
|
||||
require.NoError(t, s.batchForkChoiceAtts(context.Background()))
|
||||
|
||||
wanted, err := attaggregation.Aggregate(append(aggregatedAtts, unaggregatedAtts...))
|
||||
|
||||
@@ -42,7 +42,9 @@ func TestPruneExpired_Ticker(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(atts))
|
||||
assert.Equal(t, 2, s.cfg.Pool.AggregatedAttestationCount())
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(atts))
|
||||
for _, att := range atts {
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
|
||||
}
|
||||
|
||||
// Rewind back one epoch worth of time.
|
||||
s.genesisTime = uint64(prysmTime.Now().Unix()) - uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
@@ -95,7 +97,9 @@ func TestPruneExpired_PruneExpiredAtts(t *testing.T) {
|
||||
att4 := ðpb.Attestation{Data: ad2, AggregationBits: bitfield.Bitlist{0b1110}}
|
||||
atts := []*ethpb.Attestation{att1, att2, att3, att4}
|
||||
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(atts))
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(atts))
|
||||
for _, att := range atts {
|
||||
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
|
||||
}
|
||||
|
||||
// Rewind back one epoch worth of time.
|
||||
s.genesisTime = uint64(prysmTime.Now().Unix()) - uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
|
||||
@@ -40,30 +40,13 @@ func wrapBLSChangesArray(
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Some endpoints e.g. https://ethereum.github.io/beacon-apis/#/Validator/getAttesterDuties expect posting a top-level array of validator indices.
|
||||
// We make it more proto-friendly by wrapping it in a struct with an 'Index' field.
|
||||
func wrapValidatorIndicesArray(
|
||||
endpoint *apimiddleware.Endpoint,
|
||||
_ http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
|
||||
if _, ok := endpoint.PostRequest.(*ValidatorIndicesJson); ok {
|
||||
indices := make([]string, 0)
|
||||
if err := json.NewDecoder(req.Body).Decode(&indices); err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not decode body")
|
||||
}
|
||||
j := &ValidatorIndicesJson{Index: indices}
|
||||
b, err := json.Marshal(j)
|
||||
if err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not marshal wrapped body")
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(b))
|
||||
}
|
||||
return true, nil
|
||||
type v1alpha1SignedPhase0Block struct {
|
||||
Block *BeaconBlockJson `json:"block"` // tech debt on phase 0 called this block instead of "message"
|
||||
Signature string `json:"signature" hex:"true"`
|
||||
}
|
||||
|
||||
type phase0PublishBlockRequestJson struct {
|
||||
Phase0Block *SignedBeaconBlockJson `json:"phase0_block"`
|
||||
Message *v1alpha1SignedPhase0Block `json:"phase0_block"`
|
||||
}
|
||||
|
||||
type altairPublishBlockRequestJson struct {
|
||||
@@ -160,42 +143,40 @@ func setInitialPublishBlockPostRequest(endpoint *apimiddleware.Endpoint,
|
||||
func preparePublishedBlock(endpoint *apimiddleware.Endpoint, _ http.ResponseWriter, _ *http.Request) apimiddleware.ErrorJson {
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &phase0PublishBlockRequestJson{
|
||||
Phase0Block: block,
|
||||
endpoint.PostRequest = &phase0PublishBlockRequestJson{
|
||||
Message: &v1alpha1SignedPhase0Block{
|
||||
Block: block.Message,
|
||||
Signature: block.Signature,
|
||||
},
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockAltairJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &altairPublishBlockRequestJson{
|
||||
endpoint.PostRequest = &altairPublishBlockRequestJson{
|
||||
AltairBlock: block,
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockBellatrixJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &bellatrixPublishBlockRequestJson{
|
||||
endpoint.PostRequest = &bellatrixPublishBlockRequestJson{
|
||||
BellatrixBlock: block,
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockCapellaJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &capellaPublishBlockRequestJson{
|
||||
endpoint.PostRequest = &capellaPublishBlockRequestJson{
|
||||
CapellaBlock: block,
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockContentsDenebJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &denebPublishBlockRequestJson{
|
||||
endpoint.PostRequest = &denebPublishBlockRequestJson{
|
||||
DenebContents: block,
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
return apimiddleware.InternalServerError(errors.New("unsupported block type"))
|
||||
@@ -266,11 +247,12 @@ func setInitialPublishBlindedBlockPostRequest(endpoint *apimiddleware.Endpoint,
|
||||
// (which was filled out previously in setInitialPublishBlockPostRequest).
|
||||
func preparePublishedBlindedBlock(endpoint *apimiddleware.Endpoint, _ http.ResponseWriter, _ *http.Request) apimiddleware.ErrorJson {
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockJson); ok {
|
||||
// Prepare post request that can be properly decoded on gRPC side.
|
||||
actualPostReq := &phase0PublishBlockRequestJson{
|
||||
Phase0Block: block,
|
||||
endpoint.PostRequest = &phase0PublishBlockRequestJson{
|
||||
Message: &v1alpha1SignedPhase0Block{
|
||||
Block: block.Message,
|
||||
Signature: block.Signature,
|
||||
},
|
||||
}
|
||||
endpoint.PostRequest = actualPostReq
|
||||
return nil
|
||||
}
|
||||
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockAltairJson); ok {
|
||||
|
||||
@@ -19,47 +19,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func TestWrapValidatorIndicesArray(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &ValidatorIndicesJson{},
|
||||
}
|
||||
unwrappedIndices := []string{"1", "2"}
|
||||
unwrappedIndicesJson, err := json.Marshal(unwrappedIndices)
|
||||
require.NoError(t, err)
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(unwrappedIndicesJson)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest("POST", "http://foo.example", &body)
|
||||
|
||||
runDefault, errJson := wrapValidatorIndicesArray(endpoint, nil, request)
|
||||
require.Equal(t, true, errJson == nil)
|
||||
assert.Equal(t, apimiddleware.RunDefault(true), runDefault)
|
||||
wrappedIndices := &ValidatorIndicesJson{}
|
||||
require.NoError(t, json.NewDecoder(request.Body).Decode(wrappedIndices))
|
||||
require.Equal(t, 2, len(wrappedIndices.Index), "wrong number of wrapped items")
|
||||
assert.Equal(t, "1", wrappedIndices.Index[0])
|
||||
assert.Equal(t, "2", wrappedIndices.Index[1])
|
||||
})
|
||||
|
||||
t.Run("invalid_body", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &ValidatorIndicesJson{},
|
||||
}
|
||||
var body bytes.Buffer
|
||||
_, err := body.Write([]byte("invalid"))
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest("POST", "http://foo.example", &body)
|
||||
|
||||
runDefault, errJson := wrapValidatorIndicesArray(endpoint, nil, request)
|
||||
require.Equal(t, false, errJson == nil)
|
||||
assert.Equal(t, apimiddleware.RunDefault(false), runDefault)
|
||||
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not decode body"))
|
||||
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapBLSChangesArray(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
|
||||
@@ -16,18 +16,12 @@ func (f *BeaconEndpointFactory) IsNil() bool {
|
||||
// Paths is a collection of all valid beacon chain API paths.
|
||||
func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
return []string{
|
||||
"/eth/v1/beacon/genesis",
|
||||
"/eth/v1/beacon/states/{state_id}/root",
|
||||
"/eth/v1/beacon/states/{state_id}/fork",
|
||||
"/eth/v1/beacon/states/{state_id}/finality_checkpoints",
|
||||
"/eth/v1/beacon/states/{state_id}/validators",
|
||||
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}",
|
||||
"/eth/v1/beacon/states/{state_id}/validator_balances",
|
||||
"/eth/v1/beacon/states/{state_id}/committees",
|
||||
"/eth/v1/beacon/states/{state_id}/sync_committees",
|
||||
"/eth/v1/beacon/states/{state_id}/randao",
|
||||
"/eth/v1/beacon/headers",
|
||||
"/eth/v1/beacon/headers/{block_id}",
|
||||
"/eth/v1/beacon/blocks",
|
||||
"/eth/v1/beacon/blinded_blocks",
|
||||
"/eth/v1/beacon/blocks/{block_id}",
|
||||
@@ -51,16 +45,11 @@ func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
"/eth/v2/debug/beacon/heads",
|
||||
"/eth/v1/debug/fork_choice",
|
||||
"/eth/v1/config/fork_schedule",
|
||||
"/eth/v1/config/deposit_contract",
|
||||
"/eth/v1/config/spec",
|
||||
"/eth/v1/events",
|
||||
"/eth/v1/validator/duties/attester/{epoch}",
|
||||
"/eth/v1/validator/duties/proposer/{epoch}",
|
||||
"/eth/v1/validator/duties/sync/{epoch}",
|
||||
"/eth/v1/validator/blocks/{slot}",
|
||||
"/eth/v2/validator/blocks/{slot}",
|
||||
"/eth/v1/validator/blinded_blocks/{slot}",
|
||||
"/eth/v1/validator/liveness/{epoch}",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +57,8 @@ func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, error) {
|
||||
endpoint := apimiddleware.DefaultEndpoint()
|
||||
switch path {
|
||||
case "/eth/v1/beacon/genesis":
|
||||
endpoint.GetResponse = &GenesisResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/root":
|
||||
endpoint.GetResponse = &StateRootResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/fork":
|
||||
endpoint.GetResponse = &StateForkResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/finality_checkpoints":
|
||||
endpoint.GetResponse = &StateFinalityCheckpointResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/validators":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "id", Hex: true}, {Name: "status", Enum: true}}
|
||||
endpoint.GetResponse = &StateValidatorsResponseJson{}
|
||||
@@ -84,9 +67,6 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
case "/eth/v1/beacon/states/{state_id}/validator_balances":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "id", Hex: true}}
|
||||
endpoint.GetResponse = &ValidatorBalancesResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/committees":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}, {Name: "index"}, {Name: "slot"}}
|
||||
endpoint.GetResponse = &StateCommitteesResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/sync_committees":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
|
||||
endpoint.GetResponse = &SyncCommitteesResponseJson{}
|
||||
@@ -96,11 +76,6 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
case "/eth/v1/beacon/states/{state_id}/randao":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
|
||||
endpoint.GetResponse = &RandaoResponseJson{}
|
||||
case "/eth/v1/beacon/headers":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "slot"}, {Name: "parent_root", Hex: true}}
|
||||
endpoint.GetResponse = &BlockHeadersResponseJson{}
|
||||
case "/eth/v1/beacon/headers/{block_id}":
|
||||
endpoint.GetResponse = &BlockHeaderResponseJson{}
|
||||
case "/eth/v1/beacon/blocks":
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: setInitialPublishBlockPostRequest,
|
||||
@@ -179,32 +154,10 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
}
|
||||
case "/eth/v1/config/fork_schedule":
|
||||
endpoint.GetResponse = &ForkScheduleResponseJson{}
|
||||
case "/eth/v1/config/deposit_contract":
|
||||
endpoint.GetResponse = &DepositContractResponseJson{}
|
||||
case "/eth/v1/config/spec":
|
||||
endpoint.GetResponse = &SpecResponseJson{}
|
||||
case "/eth/v1/events":
|
||||
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleEvents}
|
||||
case "/eth/v1/validator/duties/attester/{epoch}":
|
||||
endpoint.PostRequest = &ValidatorIndicesJson{}
|
||||
endpoint.PostResponse = &AttesterDutiesResponseJson{}
|
||||
endpoint.RequestURLLiterals = []string{"epoch"}
|
||||
endpoint.Err = &NodeSyncDetailsErrorJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
|
||||
}
|
||||
case "/eth/v1/validator/duties/proposer/{epoch}":
|
||||
endpoint.GetResponse = &ProposerDutiesResponseJson{}
|
||||
endpoint.RequestURLLiterals = []string{"epoch"}
|
||||
endpoint.Err = &NodeSyncDetailsErrorJson{}
|
||||
case "/eth/v1/validator/duties/sync/{epoch}":
|
||||
endpoint.PostRequest = &ValidatorIndicesJson{}
|
||||
endpoint.PostResponse = &SyncCommitteeDutiesResponseJson{}
|
||||
endpoint.RequestURLLiterals = []string{"epoch"}
|
||||
endpoint.Err = &NodeSyncDetailsErrorJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
|
||||
}
|
||||
case "/eth/v1/validator/blocks/{slot}":
|
||||
endpoint.GetResponse = &ProduceBlockResponseJson{}
|
||||
endpoint.RequestURLLiterals = []string{"slot"}
|
||||
@@ -225,14 +178,6 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
OnPreSerializeMiddlewareResponseIntoJson: serializeProducedBlindedBlock,
|
||||
}
|
||||
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleProduceBlindedBlockSSZ}
|
||||
case "/eth/v1/validator/liveness/{epoch}":
|
||||
endpoint.PostRequest = &ValidatorIndicesJson{}
|
||||
endpoint.PostResponse = &LivenessResponseJson{}
|
||||
endpoint.RequestURLLiterals = []string{"epoch"}
|
||||
endpoint.Err = &NodeSyncDetailsErrorJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
|
||||
@@ -12,16 +12,6 @@ import (
|
||||
// Requests and responses.
|
||||
//----------------
|
||||
|
||||
type GenesisResponseJson struct {
|
||||
Data *GenesisResponse_GenesisJson `json:"data"`
|
||||
}
|
||||
|
||||
type GenesisResponse_GenesisJson struct {
|
||||
GenesisTime string `json:"genesis_time" time:"true"`
|
||||
GenesisValidatorsRoot string `json:"genesis_validators_root" hex:"true"`
|
||||
GenesisForkVersion string `json:"genesis_fork_version" hex:"true"`
|
||||
}
|
||||
|
||||
// WeakSubjectivityResponse is used to marshal/unmarshal the response for the
|
||||
// /eth/v1/beacon/weak_subjectivity endpoint.
|
||||
type WeakSubjectivityResponse struct {
|
||||
@@ -41,24 +31,6 @@ type StateRootResponse_StateRootJson struct {
|
||||
StateRoot string `json:"root" hex:"true"`
|
||||
}
|
||||
|
||||
type StateForkResponseJson struct {
|
||||
Data *ForkJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type StateFinalityCheckpointResponseJson struct {
|
||||
Data *StateFinalityCheckpointResponse_StateFinalityCheckpointJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type StateFinalityCheckpointResponse_StateFinalityCheckpointJson struct {
|
||||
PreviousJustified *CheckpointJson `json:"previous_justified"`
|
||||
CurrentJustified *CheckpointJson `json:"current_justified"`
|
||||
Finalized *CheckpointJson `json:"finalized"`
|
||||
}
|
||||
|
||||
type StateValidatorsResponseJson struct {
|
||||
Data []*ValidatorContainerJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
@@ -97,18 +69,6 @@ type RandaoResponseJson struct {
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type BlockHeadersResponseJson struct {
|
||||
Data []*BlockHeaderContainerJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type BlockHeaderResponseJson struct {
|
||||
Data *BlockHeaderContainerJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type BlockResponseJson struct {
|
||||
Data *SignedBeaconBlockJson `json:"data"`
|
||||
}
|
||||
@@ -213,27 +173,6 @@ type SpecResponseJson struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type ValidatorIndicesJson struct {
|
||||
Index []string `json:"index"`
|
||||
}
|
||||
|
||||
type AttesterDutiesResponseJson struct {
|
||||
DependentRoot string `json:"dependent_root" hex:"true"`
|
||||
Data []*AttesterDutyJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
}
|
||||
|
||||
type ProposerDutiesResponseJson struct {
|
||||
DependentRoot string `json:"dependent_root" hex:"true"`
|
||||
Data []*ProposerDutyJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
}
|
||||
|
||||
type SyncCommitteeDutiesResponseJson struct {
|
||||
Data []*SyncCommitteeDuty `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
}
|
||||
|
||||
type ProduceBlockResponseJson struct {
|
||||
Data *BeaconBlockJson `json:"data"`
|
||||
}
|
||||
@@ -300,13 +239,6 @@ type ForkChoiceResponseExtraDataJson struct {
|
||||
HeadRoot string `json:"head_root" hex:"true"`
|
||||
}
|
||||
|
||||
type LivenessResponseJson struct {
|
||||
Data []*struct {
|
||||
Index string `json:"index"`
|
||||
IsLive bool `json:"is_live"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
//----------------
|
||||
// Reusable types.
|
||||
//----------------
|
||||
@@ -740,17 +672,6 @@ type SyncAggregateJson struct {
|
||||
SyncCommitteeSignature string `json:"sync_committee_signature" hex:"true"`
|
||||
}
|
||||
|
||||
type BlockHeaderContainerJson struct {
|
||||
Root string `json:"root" hex:"true"`
|
||||
Canonical bool `json:"canonical"`
|
||||
Header *BeaconBlockHeaderContainerJson `json:"header"`
|
||||
}
|
||||
|
||||
type BeaconBlockHeaderContainerJson struct {
|
||||
Message *BeaconBlockHeaderJson `json:"message"`
|
||||
Signature string `json:"signature" hex:"true"`
|
||||
}
|
||||
|
||||
type SignedBeaconBlockHeaderJson struct {
|
||||
Header *BeaconBlockHeaderJson `json:"message"`
|
||||
Signature string `json:"signature" hex:"true"`
|
||||
@@ -1086,28 +1007,6 @@ type DepositContractJson struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type AttesterDutyJson struct {
|
||||
Pubkey string `json:"pubkey" hex:"true"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
CommitteeIndex string `json:"committee_index"`
|
||||
CommitteeLength string `json:"committee_length"`
|
||||
CommitteesAtSlot string `json:"committees_at_slot"`
|
||||
ValidatorCommitteeIndex string `json:"validator_committee_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
type ProposerDutyJson struct {
|
||||
Pubkey string `json:"pubkey" hex:"true"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
type SyncCommitteeDuty struct {
|
||||
Pubkey string `json:"pubkey" hex:"true"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
|
||||
}
|
||||
|
||||
type SignedAggregateAttestationAndProofJson struct {
|
||||
Message *AggregateAttestationAndProofJson `json:"message"`
|
||||
Signature string `json:"signature" hex:"true"`
|
||||
@@ -1335,11 +1234,6 @@ type SingleIndexedVerificationFailureJson struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type NodeSyncDetailsErrorJson struct {
|
||||
apimiddleware.DefaultErrorJson
|
||||
SyncDetails shared.SyncDetails `json:"sync_details"`
|
||||
}
|
||||
|
||||
type EventErrorJson struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Message string `json:"message"`
|
||||
|
||||
@@ -17,7 +17,7 @@ go_library(
|
||||
"validator.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api:go_default_library",
|
||||
"//api/grpc:go_default_library",
|
||||
@@ -77,7 +77,6 @@ go_library(
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -141,6 +140,7 @@ go_test(
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/migration:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/mock:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
|
||||
@@ -261,8 +261,8 @@ func (bs *Server) SubmitBlindedBlockSSZ(ctx context.Context, req *ethpbv2.SSZCon
|
||||
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, ð.GenericSignedBeaconBlock{
|
||||
Block: ð.GenericSignedBeaconBlock_BlindedDeneb{
|
||||
BlindedDeneb: ð.SignedBlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: b,
|
||||
Blobs: migration.SignedBlindedBlobsToV1Alpha1SignedBlindedBlobs(blkContent.SignedBlindedBlobSidecars),
|
||||
SignedBlindedBlock: b,
|
||||
SignedBlindedBlobSidecars: migration.SignedBlindedBlobsToV1Alpha1SignedBlindedBlobs(blkContent.SignedBlindedBlobSidecars),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -563,7 +563,7 @@ func (bs *Server) getBlindedBlockDeneb(ctx context.Context, blk interfaces.ReadO
|
||||
if blindedDenebBlk == nil {
|
||||
return nil, errNilBlock
|
||||
}
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Block)
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not convert beacon block")
|
||||
}
|
||||
@@ -601,7 +601,7 @@ func (bs *Server) getBlindedBlockDeneb(ctx context.Context, blk interfaces.ReadO
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get signed beacon block")
|
||||
}
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Block)
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not convert beacon block")
|
||||
}
|
||||
@@ -783,7 +783,7 @@ func (bs *Server) getBlindedSSZBlockDeneb(ctx context.Context, blk interfaces.Re
|
||||
if blindedDenebBlk == nil {
|
||||
return nil, errNilBlock
|
||||
}
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Block)
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get signed beacon block")
|
||||
}
|
||||
@@ -825,7 +825,7 @@ func (bs *Server) getBlindedSSZBlockDeneb(ctx context.Context, blk interfaces.Re
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get signed beacon block")
|
||||
}
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Block)
|
||||
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get signed beacon block")
|
||||
}
|
||||
@@ -905,8 +905,8 @@ func (bs *Server) submitBlindedDenebContents(ctx context.Context, blindedDenebCo
|
||||
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, ð.GenericSignedBeaconBlock{
|
||||
Block: ð.GenericSignedBeaconBlock_BlindedDeneb{
|
||||
BlindedDeneb: ð.SignedBlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: blk,
|
||||
Blobs: blobs,
|
||||
SignedBlindedBlock: blk,
|
||||
SignedBlindedBlobSidecars: blobs,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -120,7 +120,7 @@ func TestServer_GetBlindedBlock(t *testing.T) {
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
}
|
||||
|
||||
expected, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(b.Block)
|
||||
expected, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(b.Message)
|
||||
require.NoError(t, err)
|
||||
resp, err := bs.GetBlindedBlock(ctx, ðpbv1.BlockRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/filters"
|
||||
rpchelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/v1alpha1/validator"
|
||||
@@ -80,122 +79,6 @@ func (bs *Server) GetWeakSubjectivity(ctx context.Context, _ *empty.Empty) (*eth
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBlockHeader retrieves block header for given block id.
|
||||
func (bs *Server) GetBlockHeader(ctx context.Context, req *ethpbv1.BlockRequest) (*ethpbv1.BlockHeaderResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetBlockHeader")
|
||||
defer span.End()
|
||||
|
||||
blk, err := bs.Blocker.Block(ctx, req.BlockId)
|
||||
err = handleGetBlockError(blk, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v1alpha1Header, err := blk.Header()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get block header from block: %v", err)
|
||||
}
|
||||
header := migration.V1Alpha1SignedHeaderToV1(v1alpha1Header)
|
||||
headerRoot, err := header.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not hash block header: %v", err)
|
||||
}
|
||||
blkRoot, err := blk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not hash block: %v", err)
|
||||
}
|
||||
canonical, err := bs.ChainInfoFetcher.IsCanonical(ctx, blkRoot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
|
||||
}
|
||||
isOptimistic, err := bs.OptimisticModeFetcher.IsOptimisticForRoot(ctx, blkRoot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if block is optimistic: %v", err)
|
||||
}
|
||||
|
||||
return ðpbv1.BlockHeaderResponse{
|
||||
Data: ðpbv1.BlockHeaderContainer{
|
||||
Root: headerRoot[:],
|
||||
Canonical: canonical,
|
||||
Header: ðpbv1.BeaconBlockHeaderContainer{
|
||||
Message: header.Message,
|
||||
Signature: header.Signature,
|
||||
},
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: bs.FinalizationFetcher.IsFinalized(ctx, blkRoot),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListBlockHeaders retrieves block headers matching given query. By default it will fetch current head slot blocks.
|
||||
func (bs *Server) ListBlockHeaders(ctx context.Context, req *ethpbv1.BlockHeadersRequest) (*ethpbv1.BlockHeadersResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.ListBlockHeaders")
|
||||
defer span.End()
|
||||
|
||||
var err error
|
||||
var blks []interfaces.ReadOnlySignedBeaconBlock
|
||||
var blkRoots [][32]byte
|
||||
if len(req.ParentRoot) == 32 {
|
||||
blks, blkRoots, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetParentRoot(req.ParentRoot))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve blocks: %v", err)
|
||||
}
|
||||
} else {
|
||||
slot := bs.ChainInfoFetcher.HeadSlot()
|
||||
if req.Slot != nil {
|
||||
slot = *req.Slot
|
||||
}
|
||||
blks, err = bs.BeaconDB.BlocksBySlot(ctx, slot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve blocks for slot %d: %v", req.Slot, err)
|
||||
}
|
||||
_, blkRoots, err = bs.BeaconDB.BlockRootsBySlot(ctx, slot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve block roots for slot %d: %v", req.Slot, err)
|
||||
}
|
||||
}
|
||||
if len(blks) == 0 {
|
||||
return nil, status.Error(codes.NotFound, "Could not find requested blocks")
|
||||
}
|
||||
|
||||
isOptimistic := false
|
||||
isFinalized := true
|
||||
blkHdrs := make([]*ethpbv1.BlockHeaderContainer, len(blks))
|
||||
for i, bl := range blks {
|
||||
v1alpha1Header, err := bl.Header()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get block header from block: %v", err)
|
||||
}
|
||||
header := migration.V1Alpha1SignedHeaderToV1(v1alpha1Header)
|
||||
headerRoot, err := header.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not hash block header: %v", err)
|
||||
}
|
||||
canonical, err := bs.ChainInfoFetcher.IsCanonical(ctx, blkRoots[i])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
|
||||
}
|
||||
if !isOptimistic {
|
||||
isOptimistic, err = bs.OptimisticModeFetcher.IsOptimisticForRoot(ctx, blkRoots[i])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if block is optimistic: %v", err)
|
||||
}
|
||||
}
|
||||
if isFinalized {
|
||||
isFinalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoots[i])
|
||||
}
|
||||
blkHdrs[i] = ðpbv1.BlockHeaderContainer{
|
||||
Root: headerRoot[:],
|
||||
Canonical: canonical,
|
||||
Header: ðpbv1.BeaconBlockHeaderContainer{
|
||||
Message: header.Message,
|
||||
Signature: header.Signature,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ðpbv1.BlockHeadersResponse{Data: blkHdrs, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
|
||||
}
|
||||
|
||||
// SubmitBlock instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be
|
||||
// included in the beacon chain. The beacon node is not required to validate the signed ReadOnlyBeaconBlock, and a successful
|
||||
// response (20X) only indicates that the broadcast has been successful. The beacon node is expected to integrate the
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
@@ -67,292 +66,6 @@ func fillDBTestBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (
|
||||
return genBlk, blkContainers
|
||||
}
|
||||
|
||||
func TestServer_GetBlockHeader(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 123
|
||||
b.Block.ProposerIndex = 123
|
||||
b.Block.StateRoot = bytesutil.PadTo([]byte("stateroot"), 32)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
||||
|
||||
t.Run("get header", func(t *testing.T) {
|
||||
mockChainService := &mock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
bs := &Server{
|
||||
ChainInfoFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
|
||||
header, err := bs.GetBlockHeader(ctx, ðpbv1.BlockRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBodyRoot, err := sb.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
expectedParentRoot := sb.Block().ParentRoot()
|
||||
expectedHeader := ðpbv1.BeaconBlockHeader{
|
||||
Slot: sb.Block().Slot(),
|
||||
ProposerIndex: sb.Block().ProposerIndex(),
|
||||
ParentRoot: expectedParentRoot[:],
|
||||
StateRoot: bytesutil.PadTo([]byte("stateroot"), 32),
|
||||
BodyRoot: expectedBodyRoot[:],
|
||||
}
|
||||
expectedHeaderRoot, err := expectedHeader.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
headerRoot, err := header.Data.Header.Message.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedHeaderRoot, headerRoot)
|
||||
assert.Equal(t, sb.Block().Slot(), header.Data.Header.Message.Slot)
|
||||
expectedStateRoot := sb.Block().StateRoot()
|
||||
assert.DeepEqual(t, expectedStateRoot[:], header.Data.Header.Message.StateRoot)
|
||||
assert.DeepEqual(t, expectedParentRoot[:], header.Data.Header.Message.ParentRoot)
|
||||
assert.DeepEqual(t, expectedBodyRoot[:], header.Data.Header.Message.BodyRoot)
|
||||
assert.Equal(t, sb.Block().ProposerIndex(), header.Data.Header.Message.ProposerIndex)
|
||||
})
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
r, err := sb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
mockChainService := &mock.ChainService{
|
||||
OptimisticRoots: map[[32]byte]bool{r: true},
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
bs := &Server{
|
||||
ChainInfoFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
header, err := bs.GetBlockHeader(ctx, ðpbv1.BlockRequest{BlockId: []byte("head")})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, header.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 := &mock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
|
||||
bs := &Server{
|
||||
ChainInfoFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
|
||||
header, err := bs.GetBlockHeader(ctx, ðpbv1.BlockRequest{BlockId: r[:]})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, header.Finalized)
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
mockChainService := &mock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
|
||||
bs := &Server{
|
||||
ChainInfoFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
|
||||
header, err := bs.GetBlockHeader(ctx, ðpbv1.BlockRequest{BlockId: r[:]})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, header.Finalized)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ListBlockHeaders(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, 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)
|
||||
|
||||
t.Run("list headers", func(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block)
|
||||
require.NoError(t, err)
|
||||
mockChainFetcher := &mock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
FinalizedCheckPoint: ðpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
ChainInfoFetcher: mockChainFetcher,
|
||||
OptimisticModeFetcher: mockChainFetcher,
|
||||
FinalizationFetcher: mockChainFetcher,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
slot primitives.Slot
|
||||
parentRoot []byte
|
||||
want []*ethpbalpha.SignedBeaconBlock
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "slot",
|
||||
slot: primitives.Slot(30),
|
||||
want: []*ethpbalpha.SignedBeaconBlock{
|
||||
blkContainers[30].Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block,
|
||||
b1,
|
||||
b2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parent root",
|
||||
parentRoot: b1.Block.ParentRoot,
|
||||
want: []*ethpbalpha.SignedBeaconBlock{
|
||||
blkContainers[1].Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block,
|
||||
b1,
|
||||
b3,
|
||||
b4,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
headers, err := bs.ListBlockHeaders(ctx, ðpbv1.BlockHeadersRequest{
|
||||
Slot: &tt.slot,
|
||||
ParentRoot: tt.parentRoot,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(tt.want), len(headers.Data))
|
||||
for i, blk := range tt.want {
|
||||
expectedBodyRoot, err := blk.Block.Body.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
expectedHeader := ðpbv1.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)
|
||||
headerRoot, err := headers.Data[i].Header.Message.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedHeaderRoot, headerRoot)
|
||||
|
||||
assert.Equal(t, blk.Block.Slot, headers.Data[i].Header.Message.Slot)
|
||||
assert.DeepEqual(t, blk.Block.StateRoot, headers.Data[i].Header.Message.StateRoot)
|
||||
assert.DeepEqual(t, blk.Block.ParentRoot, headers.Data[i].Header.Message.ParentRoot)
|
||||
expectedRoot, err := blk.Block.Body.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedRoot[:], headers.Data[i].Header.Message.BodyRoot)
|
||||
assert.Equal(t, blk.Block.ProposerIndex, headers.Data[i].Header.Message.ProposerIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block)
|
||||
require.NoError(t, err)
|
||||
mockChainFetcher := &mock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
FinalizedCheckPoint: ðpbalpha.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)
|
||||
headers, err := bs.ListBlockHeaders(ctx, ðpbv1.BlockHeadersRequest{
|
||||
Slot: &slot,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, headers.ExecutionOptimistic)
|
||||
})
|
||||
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.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 := &mock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
FinalizedCheckPoint: ðpbalpha.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)
|
||||
headers, err := bs.ListBlockHeaders(ctx, ðpbv1.BlockHeadersRequest{
|
||||
Slot: &slot,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, headers.Finalized)
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
slot := primitives.Slot(1000)
|
||||
headers, err := bs.ListBlockHeaders(ctx, ðpbv1.BlockHeadersRequest{
|
||||
Slot: &slot,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, headers.Finalized)
|
||||
})
|
||||
t.Run("false when at least one not finalized", func(t *testing.T) {
|
||||
headers, err := bs.ListBlockHeaders(ctx, ðpbv1.BlockHeadersRequest{
|
||||
ParentRoot: []byte("parent"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, headers.Finalized)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_SubmitBlock(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
|
||||
@@ -67,19 +67,6 @@ func (_ *Server) GetSpec(ctx context.Context, _ *emptypb.Empty) (*ethpb.SpecResp
|
||||
return ðpb.SpecResponse{Data: data}, nil
|
||||
}
|
||||
|
||||
// GetDepositContract retrieves deposit contract address and genesis fork version.
|
||||
func (_ *Server) GetDepositContract(ctx context.Context, _ *emptypb.Empty) (*ethpb.DepositContractResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beaconv1.GetDepositContract")
|
||||
defer span.End()
|
||||
|
||||
return ðpb.DepositContractResponse{
|
||||
Data: ðpb.DepositContract{
|
||||
ChainId: params.BeaconConfig().DepositChainID,
|
||||
Address: params.BeaconConfig().DepositContractAddress,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareConfigSpec() (map[string]string, error) {
|
||||
data := make(map[string]string)
|
||||
config := *params.BeaconConfig()
|
||||
|
||||
@@ -387,22 +387,6 @@ func TestGetSpec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDepositContract(t *testing.T) {
|
||||
const chainId = 99
|
||||
const address = "0x0000000000000000000000000000000000000009"
|
||||
params.SetupTestConfigCleanup(t)
|
||||
config := params.BeaconConfig().Copy()
|
||||
config.DepositChainID = chainId
|
||||
config.DepositContractAddress = address
|
||||
params.OverrideBeaconConfig(config)
|
||||
|
||||
s := Server{}
|
||||
resp, err := s.GetDepositContract(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(chainId), resp.Data.ChainId)
|
||||
assert.Equal(t, address, resp.Data.Address)
|
||||
}
|
||||
|
||||
func TestForkSchedule_Ok(t *testing.T) {
|
||||
genesisForkVersion := []byte("Genesis")
|
||||
firstForkVersion, firstForkEpoch := []byte("Firs"), primitives.Epoch(100)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,16 +4,20 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
testing2 "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
@@ -25,13 +29,17 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
mock2 "github.com/prysmaticlabs/prysm/v4/testing/mock"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@@ -163,10 +171,26 @@ func TestPublishBlockV2(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.Equal(t, true, strings.Contains(writer.Body.String(), "please add the api header"))
|
||||
assert.Equal(t, true, strings.Contains(writer.Body.String(), "Body does not represent a valid block type"))
|
||||
})
|
||||
t.Run("invalid block with 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.BadCapellaBlock)))
|
||||
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)
|
||||
body := writer.Body.String()
|
||||
assert.Equal(t, true, strings.Contains(body, "Body does not represent a valid block type"))
|
||||
assert.Equal(t, true, strings.Contains(body, fmt.Sprintf("could not decode %s request body into consensus block:", version.String(version.Capella))))
|
||||
})
|
||||
t.Run("syncing", func(t *testing.T) {
|
||||
chainService := &testing2.ChainService{}
|
||||
chainService := &chainMock.ChainService{}
|
||||
server := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
||||
HeadFetcher: chainService,
|
||||
@@ -373,7 +397,7 @@ func TestPublishBlindedBlockV2(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 := shared.BlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb.Block.Block)
|
||||
converted, err := shared.BlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb.SignedBlindedBlock.Message)
|
||||
require.NoError(t, err)
|
||||
var signedblock *shared.SignedBlindedBeaconBlockContentsDeneb
|
||||
err = json.Unmarshal([]byte(rpctesting.BlindedDenebBlockContents), &signedblock)
|
||||
@@ -402,10 +426,26 @@ func TestPublishBlindedBlockV2(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlindedBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.Equal(t, true, strings.Contains(writer.Body.String(), "please add the api header"))
|
||||
assert.Equal(t, true, strings.Contains(writer.Body.String(), "Body does not represent a valid block type"))
|
||||
})
|
||||
t.Run("invalid block with 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.BadBlindedBellatrixBlock)))
|
||||
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)
|
||||
body := writer.Body.String()
|
||||
assert.Equal(t, true, strings.Contains(body, "Body does not represent a valid block type"))
|
||||
assert.Equal(t, true, strings.Contains(body, fmt.Sprintf("could not decode %s request body into consensus block:", version.String(version.Bellatrix))))
|
||||
})
|
||||
t.Run("syncing", func(t *testing.T) {
|
||||
chainService := &testing2.ChainService{}
|
||||
chainService := &chainMock.ChainService{}
|
||||
server := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
||||
HeadFetcher: chainService,
|
||||
@@ -547,7 +587,7 @@ func TestValidateEquivocation(t *testing.T) {
|
||||
fc := doublylinkedtree.New()
|
||||
require.NoError(t, fc.InsertNode(context.Background(), st, bytesutil.ToBytes32([]byte("root"))))
|
||||
server := &Server{
|
||||
ForkchoiceFetcher: &testing2.ChainService{ForkChoiceStore: fc},
|
||||
ForkchoiceFetcher: &chainMock.ChainService{ForkChoiceStore: fc},
|
||||
}
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
@@ -562,7 +602,7 @@ func TestValidateEquivocation(t *testing.T) {
|
||||
fc := doublylinkedtree.New()
|
||||
require.NoError(t, fc.InsertNode(context.Background(), st, bytesutil.ToBytes32([]byte("root"))))
|
||||
server := &Server{
|
||||
ForkchoiceFetcher: &testing2.ChainService{ForkChoiceStore: fc},
|
||||
ForkchoiceFetcher: &chainMock.ChainService{ForkChoiceStore: fc},
|
||||
}
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
@@ -576,14 +616,14 @@ func TestServer_GetBlockRoot(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
url := "http://example.com/eth/v1/beacon/blocks/{block_id}}/root"
|
||||
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 := &testing2.ChainService{
|
||||
mockChainFetcher := &chainMock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
@@ -702,7 +742,7 @@ func TestServer_GetBlockRoot(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainFetcher := &testing2.ChainService{
|
||||
mockChainFetcher := &chainMock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
@@ -737,7 +777,7 @@ func TestServer_GetBlockRoot(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*eth.BeaconBlockContainer_Phase0Block).Phase0Block)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainFetcher := &testing2.ChainService{
|
||||
mockChainFetcher := &chainMock.ChainService{
|
||||
DB: beaconDB,
|
||||
Block: wsb,
|
||||
Root: headBlock.BlockRoot,
|
||||
@@ -782,3 +822,835 @@ func TestServer_GetBlockRoot(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStateFork(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/states/{state_id}/fork", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"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 *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 = mux.SetURLVars(request, map[string]string{"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 = mux.SetURLVars(request, map[string]string{"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 := context.Background()
|
||||
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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetCommittees(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetCommitteesResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetBlockHeaders(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, 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)
|
||||
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,
|
||||
OptimisticModeFetcher: mockChainFetcher,
|
||||
FinalizationFetcher: mockChainFetcher,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
slot primitives.Slot
|
||||
parentRoot string
|
||||
want []*eth.SignedBeaconBlock
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "slot",
|
||||
slot: primitives.Slot(30),
|
||||
parentRoot: "",
|
||||
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=%d&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)
|
||||
resp := &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, shared.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)
|
||||
resp := &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)
|
||||
resp := &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)
|
||||
resp := &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)
|
||||
resp := &GetBlockHeadersResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, false, resp.Finalized)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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 = mux.SetURLVars(request, map[string]string{"block_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockHeader(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetBlockHeaderResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.Data.Canonical)
|
||||
assert.Equal(t, "0x725b389a0e5a7623fa7600b9e5cb6248d5c293fc2f5877e42a665b44f40c85f6", 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 := &http2.DefaultErrorJson{}
|
||||
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 = mux.SetURLVars(request, map[string]string{"block_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockHeader(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"block_id": hexutil.Encode(r[:])})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockHeader(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"block_id": hexutil.Encode(r[:])})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockHeader(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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)
|
||||
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetFinalityCheckpoints(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 := &http2.DefaultErrorJson{}
|
||||
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("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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetFinalityCheckpoints(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetFinalityCheckpoints(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &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 := &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 := &http2.DefaultErrorJson{}
|
||||
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 := &http2.DefaultErrorJson{}
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
@@ -12,13 +11,10 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
eth2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type stateRequest struct {
|
||||
@@ -26,33 +22,6 @@ type stateRequest struct {
|
||||
stateId []byte
|
||||
}
|
||||
|
||||
// GetGenesis retrieves details of the chain's genesis which can be used to identify chain.
|
||||
func (bs *Server) GetGenesis(ctx context.Context, _ *emptypb.Empty) (*ethpb.GenesisResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetGenesis")
|
||||
defer span.End()
|
||||
|
||||
genesisTime := bs.GenesisTimeFetcher.GenesisTime()
|
||||
if genesisTime.IsZero() {
|
||||
return nil, status.Errorf(codes.NotFound, "Chain genesis info is not yet known")
|
||||
}
|
||||
validatorRoot := bs.ChainInfoFetcher.GenesisValidatorsRoot()
|
||||
if bytes.Equal(validatorRoot[:], params.BeaconConfig().ZeroHash[:]) {
|
||||
return nil, status.Errorf(codes.NotFound, "Chain genesis info is not yet known")
|
||||
}
|
||||
forkVersion := params.BeaconConfig().GenesisForkVersion
|
||||
|
||||
return ðpb.GenesisResponse{
|
||||
Data: ðpb.GenesisResponse_Genesis{
|
||||
GenesisTime: ×tamppb.Timestamp{
|
||||
Seconds: genesisTime.Unix(),
|
||||
Nanos: 0,
|
||||
},
|
||||
GenesisValidatorsRoot: validatorRoot[:],
|
||||
GenesisForkVersion: forkVersion,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStateRoot calculates HashTreeRoot for state with given 'stateId'. If stateId is root, same value will be returned.
|
||||
func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateRootResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetStateRoot")
|
||||
@@ -90,68 +59,6 @@ func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*e
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStateFork returns Fork object for state with given 'stateId'.
|
||||
func (bs *Server) GetStateFork(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateForkResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetStateFork")
|
||||
defer span.End()
|
||||
|
||||
st, err := bs.Stater.State(ctx, req.StateId)
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
fork := st.Fork()
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ðpb.StateForkResponse{
|
||||
Data: ðpb.Fork{
|
||||
PreviousVersion: fork.PreviousVersion,
|
||||
CurrentVersion: fork.CurrentVersion,
|
||||
Epoch: fork.Epoch,
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFinalityCheckpoints returns finality checkpoints for state with given 'stateId'. In case finality is
|
||||
// not yet achieved, checkpoint should return epoch 0 and ZERO_HASH as root.
|
||||
func (bs *Server) GetFinalityCheckpoints(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateFinalityCheckpointResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetFinalityCheckpoints")
|
||||
defer span.End()
|
||||
|
||||
st, err := bs.Stater.State(ctx, req.StateId)
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ðpb.StateFinalityCheckpointResponse{
|
||||
Data: ðpb.StateFinalityCheckpointResponse_StateFinalityCheckpoint{
|
||||
PreviousJustified: checkpoint(st.PreviousJustifiedCheckpoint()),
|
||||
CurrentJustified: checkpoint(st.CurrentJustifiedCheckpoint()),
|
||||
Finalized: checkpoint(st.FinalizedCheckpoint()),
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetRandao fetches the RANDAO mix for the requested epoch from the state identified by state_id.
|
||||
// If an epoch is not specified then the RANDAO mix for the state's current epoch will be returned.
|
||||
// By adjusting the state_id parameter you can query for any historic value of the RANDAO mix.
|
||||
@@ -228,16 +135,3 @@ func (bs *Server) stateFromRequest(ctx context.Context, req *stateRequest) (stat
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func checkpoint(sourceCheckpoint *eth.Checkpoint) *ethpb.Checkpoint {
|
||||
if sourceCheckpoint != nil {
|
||||
return ðpb.Checkpoint{
|
||||
Epoch: sourceCheckpoint.Epoch,
|
||||
Root: sourceCheckpoint.Root,
|
||||
}
|
||||
}
|
||||
return ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: params.BeaconConfig().ZeroHash[:],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package beacon
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
@@ -13,66 +12,11 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
eth2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func TestGetGenesis(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
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,
|
||||
}
|
||||
resp, err := s.GetGenesis(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, genesis.Unix(), resp.Data.GenesisTime.Seconds)
|
||||
assert.Equal(t, int32(0), resp.Data.GenesisTime.Nanos)
|
||||
assert.DeepEqual(t, validatorsRoot[:], resp.Data.GenesisValidatorsRoot)
|
||||
assert.DeepEqual(t, []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,
|
||||
}
|
||||
_, err := s.GetGenesis(ctx, &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "Chain genesis info is not yet known", err)
|
||||
})
|
||||
|
||||
t.Run("No genesis validators root", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{
|
||||
Genesis: genesis,
|
||||
ValidatorsRoot: [32]byte{},
|
||||
}
|
||||
s := Server{
|
||||
GenesisTimeFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
_, err := s.GetGenesis(ctx, &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "Chain genesis info is not yet known", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStateRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fakeState, err := util.NewBeaconState()
|
||||
@@ -163,207 +107,6 @@ func TestGetStateRoot(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStateFork(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fillFork := func(state *ethpb.BeaconState) error {
|
||||
state.Fork = ðpb.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,
|
||||
}
|
||||
|
||||
resp, err := server.GetStateFork(ctx, ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
expectedFork := fakeState.Fork()
|
||||
assert.Equal(t, expectedFork.Epoch, resp.Data.Epoch)
|
||||
assert.DeepEqual(t, expectedFork.CurrentVersion, resp.Data.CurrentVersion)
|
||||
assert.DeepEqual(t, expectedFork.PreviousVersion, resp.Data.PreviousVersion)
|
||||
|
||||
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}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetStateFork(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(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 := 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,
|
||||
}
|
||||
resp, err := server.GetStateFork(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFinalityCheckpoints(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fillCheckpoints := func(state *ethpb.BeaconState) error {
|
||||
state.PreviousJustifiedCheckpoint = ðpb.Checkpoint{
|
||||
Root: bytesutil.PadTo([]byte("previous"), 32),
|
||||
Epoch: 113,
|
||||
}
|
||||
state.CurrentJustifiedCheckpoint = ðpb.Checkpoint{
|
||||
Root: bytesutil.PadTo([]byte("current"), 32),
|
||||
Epoch: 123,
|
||||
}
|
||||
state.FinalizedCheckpoint = ðpb.Checkpoint{
|
||||
Root: bytesutil.PadTo([]byte("finalized"), 32),
|
||||
Epoch: 103,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fakeState, err := util.NewBeaconState(fillCheckpoints)
|
||||
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,
|
||||
}
|
||||
|
||||
resp, err := server.GetFinalityCheckpoints(ctx, ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, fakeState.FinalizedCheckpoint().Epoch, resp.Data.Finalized.Epoch)
|
||||
assert.DeepEqual(t, fakeState.FinalizedCheckpoint().Root, resp.Data.Finalized.Root)
|
||||
assert.Equal(t, fakeState.CurrentJustifiedCheckpoint().Epoch, resp.Data.CurrentJustified.Epoch)
|
||||
assert.DeepEqual(t, fakeState.CurrentJustifiedCheckpoint().Root, resp.Data.CurrentJustified.Root)
|
||||
assert.Equal(t, fakeState.PreviousJustifiedCheckpoint().Epoch, resp.Data.PreviousJustified.Epoch)
|
||||
assert.DeepEqual(t, fakeState.PreviousJustifiedCheckpoint().Root, resp.Data.PreviousJustified.Root)
|
||||
|
||||
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}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetFinalityCheckpoints(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(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 := 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,
|
||||
}
|
||||
resp, err := server.GetFinalityCheckpoints(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandao(t *testing.T) {
|
||||
mixCurrent := bytesutil.PadTo([]byte("current"), 32)
|
||||
mixOld := bytesutil.PadTo([]byte("old"), 32)
|
||||
|
||||
@@ -12,6 +12,19 @@ type BlockRootResponse struct {
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type GetCommitteesResponse struct {
|
||||
Data []*shared.Committee `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type DepositContractResponse struct {
|
||||
Data *struct {
|
||||
ChainId uint64 `json:"chain_id"`
|
||||
Address string `json:"address"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type ListAttestationsResponse struct {
|
||||
Data []*shared.Attestation `json:"data"`
|
||||
}
|
||||
@@ -27,3 +40,43 @@ type ListVoluntaryExitsResponse struct {
|
||||
type SubmitSyncCommitteeSignaturesRequest struct {
|
||||
Data []*shared.SyncCommitteeMessage `json:"data"`
|
||||
}
|
||||
|
||||
type GetStateForkResponse struct {
|
||||
Data *shared.Fork `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type GetFinalityCheckpointsResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *FinalityCheckpoints `json:"data"`
|
||||
}
|
||||
|
||||
type FinalityCheckpoints struct {
|
||||
PreviousJustified *shared.Checkpoint `json:"previous_justified"`
|
||||
CurrentJustified *shared.Checkpoint `json:"current_justified"`
|
||||
Finalized *shared.Checkpoint `json:"finalized"`
|
||||
}
|
||||
|
||||
type GetGenesisResponse struct {
|
||||
Data *Genesis `json:"data"`
|
||||
}
|
||||
|
||||
type Genesis struct {
|
||||
GenesisTime string `json:"genesis_time"`
|
||||
GenesisValidatorsRoot string `json:"genesis_validators_root"`
|
||||
GenesisForkVersion string `json:"genesis_fork_version"`
|
||||
}
|
||||
|
||||
type GetBlockHeadersResponse struct {
|
||||
Data []*shared.SignedBeaconBlockHeaderContainer `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type GetBlockHeaderResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *shared.SignedBeaconBlockHeaderContainer `json:"data"`
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corehelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
@@ -170,71 +169,6 @@ func (bs *Server) ListValidatorBalances(ctx context.Context, req *ethpb.Validato
|
||||
return ðpb.ValidatorBalancesResponse{Data: valBalances, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
|
||||
}
|
||||
|
||||
// ListCommittees retrieves the committees for the given state at the given epoch.
|
||||
// If the requested slot and index are defined, only those committees are returned.
|
||||
func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommitteesRequest) (*ethpb.StateCommitteesResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.ListCommittees")
|
||||
defer span.End()
|
||||
|
||||
st, err := bs.Stater.State(ctx, req.StateId)
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
|
||||
epoch := slots.ToEpoch(st.Slot())
|
||||
if req.Epoch != nil {
|
||||
epoch = *req.Epoch
|
||||
}
|
||||
activeCount, err := corehelpers.ActiveValidatorCount(ctx, st, epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
|
||||
}
|
||||
|
||||
startSlot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
|
||||
}
|
||||
endSlot, err := slots.EpochEnd(epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
|
||||
}
|
||||
committeesPerSlot := corehelpers.SlotCommitteeCount(activeCount)
|
||||
committees := make([]*ethpb.Committee, 0)
|
||||
for slot := startSlot; slot <= endSlot; slot++ {
|
||||
if req.Slot != nil && slot != *req.Slot {
|
||||
continue
|
||||
}
|
||||
for index := primitives.CommitteeIndex(0); index < primitives.CommitteeIndex(committeesPerSlot); index++ {
|
||||
if req.Index != nil && index != *req.Index {
|
||||
continue
|
||||
}
|
||||
committee, err := corehelpers.BeaconCommitteeFromState(ctx, st, slot, index)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get committee: %v", err)
|
||||
}
|
||||
committeeContainer := ðpb.Committee{
|
||||
Index: index,
|
||||
Slot: slot,
|
||||
Validators: committee,
|
||||
}
|
||||
committees = append(committees, committeeContainer)
|
||||
}
|
||||
}
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ðpb.StateCommitteesResponse{Data: committees, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
|
||||
}
|
||||
|
||||
// This function returns the validator object based on the passed in ID. The validator ID could be its public key,
|
||||
// or its index.
|
||||
func valContainersByRequestIds(state state.BeaconState, validatorIds [][]byte) ([]*ethpb.ValidatorContainer, error) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func TestGetValidator(t *testing.T) {
|
||||
@@ -623,6 +622,7 @@ func TestListValidators_Status(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestListValidatorBalances(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := dbTest.SetupDB(t)
|
||||
@@ -780,201 +780,3 @@ func TestListValidatorBalances(t *testing.T) {
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListCommittees(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 8192)
|
||||
epoch := slots.ToEpoch(st.Slot())
|
||||
|
||||
t.Run("Head All Committees", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int(params.BeaconConfig().SlotsPerEpoch)*2, len(resp.Data))
|
||||
for _, datum := range resp.Data {
|
||||
assert.Equal(t, true, datum.Index == primitives.CommitteeIndex(0) || datum.Index == primitives.CommitteeIndex(1))
|
||||
assert.Equal(t, epoch, slots.ToEpoch(datum.Slot))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Head All Committees of Epoch 10", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
epoch := primitives.Epoch(10)
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
Epoch: &epoch,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
for _, datum := range resp.Data {
|
||||
assert.Equal(t, true, datum.Slot >= 320 && datum.Slot <= 351)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Head All Committees of Slot 4", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
slot := primitives.Slot(4)
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
Slot: &slot,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
index := primitives.CommitteeIndex(0)
|
||||
for _, datum := range resp.Data {
|
||||
assert.Equal(t, epoch, slots.ToEpoch(datum.Slot))
|
||||
assert.Equal(t, slot, datum.Slot)
|
||||
assert.Equal(t, index, datum.Index)
|
||||
index++
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Head All Committees of Index 1", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
index := primitives.CommitteeIndex(1)
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
Index: &index,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int(params.BeaconConfig().SlotsPerEpoch), len(resp.Data))
|
||||
slot := primitives.Slot(0)
|
||||
for _, datum := range resp.Data {
|
||||
assert.Equal(t, epoch, slots.ToEpoch(datum.Slot))
|
||||
assert.Equal(t, slot, datum.Slot)
|
||||
assert.Equal(t, index, datum.Index)
|
||||
slot++
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Head All Committees of Slot 2, Index 1", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
index := primitives.CommitteeIndex(1)
|
||||
slot := primitives.Slot(2)
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
Slot: &slot,
|
||||
Index: &index,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
for _, datum := range resp.Data {
|
||||
assert.Equal(t, epoch, slots.ToEpoch(datum.Slot))
|
||||
assert.Equal(t, slot, datum.Slot)
|
||||
assert.Equal(t, index, datum.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,
|
||||
}
|
||||
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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,
|
||||
}
|
||||
|
||||
resp, err := s.ListCommittees(ctx, ðpb.StateCommitteesRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
|
||||
@@ -3,9 +3,11 @@ package helpers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
@@ -25,6 +27,23 @@ func PrepareStateFetchGRPCError(err error) error {
|
||||
return status.Errorf(codes.Internal, "Invalid state ID: %v", err)
|
||||
}
|
||||
|
||||
// HandleStateFetchError writes the appropriate HTTP error based on the supplied error.
|
||||
func HandleStateFetchError(w http.ResponseWriter, err error) {
|
||||
if errors.Is(err, stategen.ErrNoDataForSlot) {
|
||||
http2.HandleError(w, "Lacking historical data needed to fulfill request", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if stateNotFoundErr, ok := err.(*lookup.StateNotFoundError); ok {
|
||||
http2.HandleError(w, "State not found: "+stateNotFoundErr.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if parseErr, ok := err.(*lookup.StateIdParseError); ok {
|
||||
http2.HandleError(w, "Invalid state ID: "+parseErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
http2.HandleError(w, "Invalid state ID: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// IndexedVerificationFailure represents a collection of verification failures.
|
||||
type IndexedVerificationFailure struct {
|
||||
Failures []*SingleIndexedVerificationFailure `json:"failures"`
|
||||
|
||||
@@ -13,8 +13,11 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -2,7 +2,13 @@ package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
)
|
||||
|
||||
// DecodeError represents an error resulting from trying to decode an HTTP request.
|
||||
@@ -43,3 +49,21 @@ type IndexedVerificationFailure struct {
|
||||
Index int `json:"index"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// WriteBlockFetchError writes an appropriate error based on the supplied argument.
|
||||
// The argument error should be a result of fetching block.
|
||||
func WriteBlockFetchError(w http.ResponseWriter, blk interfaces.ReadOnlySignedBeaconBlock, err error) bool {
|
||||
if invalidBlockIdErr, ok := err.(*lookup.BlockIdParseError); ok {
|
||||
http2.HandleError(w, "Invalid block ID: "+invalidBlockIdErr.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get block from block ID: %s"+err.Error(), http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
if err = blocks.BeaconBlockIsNil(blk); err != nil {
|
||||
http2.HandleError(w, "Could not find requested block: %s"+err.Error(), http.StatusNotFound)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -30,6 +30,12 @@ type Checkpoint struct {
|
||||
Root string `json:"root" validate:"required,hexadecimal"`
|
||||
}
|
||||
|
||||
type Committee struct {
|
||||
Index string `json:"index"`
|
||||
Slot string `json:"slot"`
|
||||
Validators []string `json:"validators"`
|
||||
}
|
||||
|
||||
type SignedContributionAndProof struct {
|
||||
Message *ContributionAndProof `json:"message" validate:"required"`
|
||||
Signature string `json:"signature" validate:"required,hexadecimal"`
|
||||
@@ -101,6 +107,32 @@ type VoluntaryExit struct {
|
||||
ValidatorIndex string `json:"validator_index" validate:"required,number,gte=0"`
|
||||
}
|
||||
|
||||
type Fork struct {
|
||||
PreviousVersion string `json:"previous_version"`
|
||||
CurrentVersion string `json:"current_version"`
|
||||
Epoch string `json:"epoch"`
|
||||
}
|
||||
|
||||
func (s *Fork) ToConsensus() (*eth.Fork, error) {
|
||||
previousVersion, err := hexutil.Decode(s.PreviousVersion)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "PreviousVersion")
|
||||
}
|
||||
currentVersion, err := hexutil.Decode(s.CurrentVersion)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "CurrentVersion")
|
||||
}
|
||||
epoch, err := strconv.ParseUint(s.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Epoch")
|
||||
}
|
||||
return ð.Fork{
|
||||
PreviousVersion: previousVersion,
|
||||
CurrentVersion: currentVersion,
|
||||
Epoch: primitives.Epoch(epoch),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SyncCommitteeMessage struct {
|
||||
Slot string `json:"slot" validate:"required,number,gte=0"`
|
||||
BeaconBlockRoot string `json:"beacon_block_root" validate:"required,hexadecimal"`
|
||||
|
||||
@@ -311,6 +311,12 @@ type DepositData struct {
|
||||
Signature string `json:"signature" validate:"required"`
|
||||
}
|
||||
|
||||
type SignedBeaconBlockHeaderContainer struct {
|
||||
Header *SignedBeaconBlockHeader `json:"header"`
|
||||
Root string `json:"root"`
|
||||
Canonical bool `json:"canonical"`
|
||||
}
|
||||
|
||||
type SignedBeaconBlockHeader struct {
|
||||
Message *BeaconBlockHeader `json:"message" validate:"required"`
|
||||
Signature string `json:"signature" validate:"required"`
|
||||
|
||||
@@ -423,7 +423,7 @@ func (b *BeaconBlockBellatrix) ToConsensus() (*eth.BeaconBlockBellatrix, error)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.BaseFeePerGas")
|
||||
}
|
||||
@@ -638,7 +638,7 @@ func (b *BlindedBeaconBlockBellatrix) ToConsensus() (*eth.BlindedBeaconBlockBell
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.BaseFeePerGas")
|
||||
}
|
||||
@@ -845,7 +845,7 @@ func (b *BeaconBlockCapella) ToConsensus() (*eth.BeaconBlockCapella, error) {
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.BaseFeePerGas")
|
||||
}
|
||||
@@ -1095,7 +1095,7 @@ func (b *BlindedBeaconBlockCapella) ToConsensus() (*eth.BlindedBeaconBlockCapell
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.BaseFeePerGas")
|
||||
}
|
||||
@@ -1268,8 +1268,8 @@ func (b *SignedBlindedBeaconBlockContentsDeneb) ToGeneric() (*eth.GenericSignedB
|
||||
return nil, NewDecodeError(err, "SignedBlindedBlock")
|
||||
}
|
||||
block := ð.SignedBlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: signedBlindedBlock,
|
||||
Blobs: signedBlindedBlobSidecars,
|
||||
SignedBlindedBlock: signedBlindedBlock,
|
||||
SignedBlindedBlobSidecars: signedBlindedBlobSidecars,
|
||||
}
|
||||
return ð.GenericSignedBeaconBlock{Block: ð.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: block}, IsBlinded: true, PayloadValue: 0 /* can't get payload value from blinded block */}, nil
|
||||
}
|
||||
@@ -1444,7 +1444,7 @@ func (b *BeaconBlockDeneb) ToConsensus() (*eth.BeaconBlockDeneb, error) {
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayload.BaseFeePerGas")
|
||||
}
|
||||
@@ -1706,7 +1706,7 @@ func (b *SignedBlindedBeaconBlockDeneb) ToConsensus() (*eth.SignedBlindedBeaconB
|
||||
return nil, err
|
||||
}
|
||||
return ð.SignedBlindedBeaconBlockDeneb{
|
||||
Block: blindedBlock,
|
||||
Message: blindedBlock,
|
||||
Signature: sig,
|
||||
}, nil
|
||||
}
|
||||
@@ -1836,7 +1836,7 @@ func (b *BlindedBeaconBlockDeneb) ToConsensus() (*eth.BlindedBeaconBlockDeneb, e
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.ExtraData")
|
||||
}
|
||||
payloadBaseFeePerGas, err := uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
payloadBaseFeePerGas, err := Uint256ToSSZBytes(b.Body.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Body.ExecutionPayloadHeader.BaseFeePerGas")
|
||||
}
|
||||
@@ -1994,6 +1994,16 @@ func (s *BlindedBlobSidecar) ToConsensus() (*eth.BlindedBlobSidecar, error) {
|
||||
return bsc, nil
|
||||
}
|
||||
|
||||
func BeaconBlockHeaderFromConsensus(h *eth.BeaconBlockHeader) *BeaconBlockHeader {
|
||||
return &BeaconBlockHeader{
|
||||
Slot: strconv.FormatUint(uint64(h.Slot), 10),
|
||||
ProposerIndex: strconv.FormatUint(uint64(h.ProposerIndex), 10),
|
||||
ParentRoot: hexutil.Encode(h.ParentRoot),
|
||||
StateRoot: hexutil.Encode(h.StateRoot),
|
||||
BodyRoot: hexutil.Encode(h.BodyRoot),
|
||||
}
|
||||
}
|
||||
|
||||
func BeaconBlockFromConsensus(b *eth.BeaconBlock) (*BeaconBlock, error) {
|
||||
proposerSlashings, err := ProposerSlashingsFromConsensus(b.Body.ProposerSlashings)
|
||||
if err != nil {
|
||||
@@ -2405,6 +2415,28 @@ func BlindedBeaconBlockContentsDenebFromConsensus(b *eth.BlindedBeaconBlockAndBl
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlindedBeaconBlockContentsDenebFromConsensus(b *eth.SignedBlindedBeaconBlockAndBlobsDeneb) (*SignedBlindedBeaconBlockContentsDeneb, error) {
|
||||
var blindedBlobSidecars []*SignedBlindedBlobSidecar
|
||||
if len(b.SignedBlindedBlobSidecars) != 0 {
|
||||
blindedBlobSidecars = make([]*SignedBlindedBlobSidecar, len(b.SignedBlindedBlobSidecars))
|
||||
for i, s := range b.SignedBlindedBlobSidecars {
|
||||
signedBlob, err := SignedBlindedBlobSidecarFromConsensus(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blindedBlobSidecars[i] = signedBlob
|
||||
}
|
||||
}
|
||||
blindedBlock, err := SignedBlindedBeaconBlockDenebFromConsensus(b.SignedBlindedBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBlindedBeaconBlockContentsDeneb{
|
||||
SignedBlindedBlock: blindedBlock,
|
||||
SignedBlindedBlobSidecars: blindedBlobSidecars,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BeaconBlockContentsDenebFromConsensus(b *eth.BeaconBlockAndBlobsDeneb) (*BeaconBlockContentsDeneb, error) {
|
||||
var blobSidecars []*BlobSidecar
|
||||
if len(b.Blobs) != 0 {
|
||||
@@ -2427,6 +2459,28 @@ func BeaconBlockContentsDenebFromConsensus(b *eth.BeaconBlockAndBlobsDeneb) (*Be
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBeaconBlockContentsDenebFromConsensus(b *eth.SignedBeaconBlockAndBlobsDeneb) (*SignedBeaconBlockContentsDeneb, error) {
|
||||
var blobSidecars []*SignedBlobSidecar
|
||||
if len(b.Blobs) != 0 {
|
||||
blobSidecars = make([]*SignedBlobSidecar, len(b.Blobs))
|
||||
for i, s := range b.Blobs {
|
||||
blob, err := SignedBlobSidecarFromConsensus(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blobSidecars[i] = blob
|
||||
}
|
||||
}
|
||||
block, err := SignedBeaconBlockDenebFromConsensus(b.Block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBeaconBlockContentsDeneb{
|
||||
SignedBlock: block,
|
||||
SignedBlobSidecars: blobSidecars,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlindedBeaconBlockDenebFromConsensus(b *eth.BlindedBeaconBlockDeneb) (*BlindedBeaconBlockDeneb, error) {
|
||||
proposerSlashings, err := ProposerSlashingsFromConsensus(b.Body.ProposerSlashings)
|
||||
if err != nil {
|
||||
@@ -2509,6 +2563,17 @@ func BlindedBeaconBlockDenebFromConsensus(b *eth.BlindedBeaconBlockDeneb) (*Blin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlindedBeaconBlockDenebFromConsensus(b *eth.SignedBlindedBeaconBlockDeneb) (*SignedBlindedBeaconBlockDeneb, error) {
|
||||
block, err := BlindedBeaconBlockDenebFromConsensus(b.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBlindedBeaconBlockDeneb{
|
||||
Message: block,
|
||||
Signature: hexutil.Encode(b.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BeaconBlockDenebFromConsensus(b *eth.BeaconBlockDeneb) (*BeaconBlockDeneb, error) {
|
||||
proposerSlashings, err := ProposerSlashingsFromConsensus(b.Body.ProposerSlashings)
|
||||
if err != nil {
|
||||
@@ -2603,6 +2668,17 @@ func BeaconBlockDenebFromConsensus(b *eth.BeaconBlockDeneb) (*BeaconBlockDeneb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBeaconBlockDenebFromConsensus(b *eth.SignedBeaconBlockDeneb) (*SignedBeaconBlockDeneb, error) {
|
||||
beaconBlock, err := BeaconBlockDenebFromConsensus(b.Block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBeaconBlockDeneb{
|
||||
Message: beaconBlock,
|
||||
Signature: hexutil.Encode(b.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlindedBlobSidecarFromConsensus(b *eth.BlindedBlobSidecar) (*BlindedBlobSidecar, error) {
|
||||
return &BlindedBlobSidecar{
|
||||
BlockRoot: hexutil.Encode(b.BlockRoot),
|
||||
@@ -2616,6 +2692,17 @@ func BlindedBlobSidecarFromConsensus(b *eth.BlindedBlobSidecar) (*BlindedBlobSid
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlindedBlobSidecarFromConsensus(b *eth.SignedBlindedBlobSidecar) (*SignedBlindedBlobSidecar, error) {
|
||||
blobSidecar, err := BlindedBlobSidecarFromConsensus(b.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBlindedBlobSidecar{
|
||||
Message: blobSidecar,
|
||||
Signature: hexutil.Encode(b.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlobSidecarFromConsensus(b *eth.BlobSidecar) (*BlobSidecar, error) {
|
||||
return &BlobSidecar{
|
||||
BlockRoot: hexutil.Encode(b.BlockRoot),
|
||||
@@ -2629,6 +2716,17 @@ func BlobSidecarFromConsensus(b *eth.BlobSidecar) (*BlobSidecar, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlobSidecarFromConsensus(b *eth.SignedBlobSidecar) (*SignedBlobSidecar, error) {
|
||||
blobSidecar, err := BlobSidecarFromConsensus(b.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBlobSidecar{
|
||||
Message: blobSidecar,
|
||||
Signature: hexutil.Encode(b.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ProposerSlashingsToConsensus(src []*ProposerSlashing) ([]*eth.ProposerSlashing, error) {
|
||||
if src == nil {
|
||||
return nil, errNilValue
|
||||
@@ -3080,7 +3178,7 @@ func BlsChangesFromConsensus(src []*eth.SignedBLSToExecutionChange) ([]*SignedBl
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func uint256ToSSZBytes(num string) ([]byte, error) {
|
||||
func Uint256ToSSZBytes(num string) ([]byte, error) {
|
||||
uint256, ok := new(big.Int).SetString(num, 10)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse Uint256")
|
||||
|
||||
@@ -677,6 +677,188 @@ const (
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}`
|
||||
|
||||
// BadBlindedBellatrixBlock contains wrong data to create a block that does not pass ToConsensus conversion
|
||||
// "parent_root" length too short
|
||||
// "block_hash" length too short
|
||||
// "state_root" length too short
|
||||
BadBlindedBellatrixBlock = `{
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e95872",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"body": {
|
||||
"randao_reveal": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"eth1_data": {
|
||||
"deposit_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"deposit_count": "1",
|
||||
"block_hash": "0xcf8e0d4e95872"
|
||||
},
|
||||
"graffiti": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"proposer_slashings": [
|
||||
{
|
||||
"signed_header_1": {
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e9580f2",
|
||||
"body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
},
|
||||
"signed_header_2": {
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attester_slashings": [
|
||||
{
|
||||
"attestation_1": {
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"attestation_2": {
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"attestations": [
|
||||
{
|
||||
"aggregation_bits": "0xffffffffffffffffffffffffffffffffff3f",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"deposits": [
|
||||
{
|
||||
"proof": [
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
],
|
||||
"data": {
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"amount": "1",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
}
|
||||
],
|
||||
"voluntary_exits": [
|
||||
{
|
||||
"message": {
|
||||
"epoch": "1",
|
||||
"validator_index": "1"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
],
|
||||
"sync_aggregate": {
|
||||
"sync_committee_bits": "0x6451e9f951ebf05edc01de67e593484b672877054f055903ff0df1a1a945cf30ca26bb4d4b154f94a1bc776bcf5d0efb3603e1f9b8ee2499ccdcfe2a18cef458",
|
||||
"sync_committee_signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
},
|
||||
"execution_payload_header": {
|
||||
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"block_number": "1",
|
||||
"gas_limit": "1",
|
||||
"gas_used": "1",
|
||||
"timestamp": "1",
|
||||
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"base_fee_per_gas": "1",
|
||||
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}`
|
||||
CapellaBlock = `{
|
||||
"message": {
|
||||
"slot": "1",
|
||||
@@ -873,6 +1055,208 @@ const (
|
||||
}
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}`
|
||||
// BadCapellaBlock contains wrong data to create a block that does not pass ToConsensus conversion
|
||||
// "state_root" length too short
|
||||
// "block_hash" length too short
|
||||
// "graffiti" length too short
|
||||
// "state_root" length too short
|
||||
BadCapellaBlock = `{
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e957e8208d920f2",
|
||||
"body": {
|
||||
"randao_reveal": "0x1b66ac1fb663c9baf888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"eth1_data": {
|
||||
"deposit_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"deposit_count": "1",
|
||||
"block_hash": "0xcf8e0d4e95873691884560367e8208d920f2"
|
||||
},
|
||||
"graffiti": "0xcf8e0d4e9587369b230120f2",
|
||||
"proposer_slashings": [
|
||||
{
|
||||
"signed_header_1": {
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d208d920f2",
|
||||
"body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
},
|
||||
"signed_header_2": {
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attester_slashings": [
|
||||
{
|
||||
"attestation_1": {
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"attestation_2": {
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"attestations": [
|
||||
{
|
||||
"aggregation_bits": "0xffffffffffffffffffffffffffffffffff3f",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"deposits": [
|
||||
{
|
||||
"proof": [
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
],
|
||||
"data": {
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"amount": "1",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
}
|
||||
],
|
||||
"voluntary_exits": [
|
||||
{
|
||||
"message": {
|
||||
"epoch": "1",
|
||||
"validator_index": "1"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
],
|
||||
"sync_aggregate": {
|
||||
"sync_committee_bits": "0x6451e9f951ebf05edc01de67e593484b672877054f055903ff0df1a1a945cf30ca26bb4d4b154f94a1bc776bcf5d0efb3603e1f9b8ee2499ccdcfe2a18cef458",
|
||||
"sync_committee_signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
},
|
||||
"execution_payload": {
|
||||
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"block_number": "1",
|
||||
"gas_limit": "1",
|
||||
"gas_used": "1",
|
||||
"timestamp": "1",
|
||||
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"base_fee_per_gas": "14074904626401341155369551180448584754667373453244490859944217516317499064576",
|
||||
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"transactions": [
|
||||
"0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"index": "1",
|
||||
"validator_index": "1",
|
||||
"address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bls_to_execution_changes": [
|
||||
{
|
||||
"message": {
|
||||
"validator_index": "1",
|
||||
"from_bls_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"to_execution_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09"
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}`
|
||||
BlindedCapellaBlock = `{
|
||||
"message": {
|
||||
|
||||
@@ -45,6 +45,7 @@ go_library(
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_go_playground_validator_v10//:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
@@ -99,6 +100,7 @@ go_test(
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_golang_mock//gomock:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
],
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/builder"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
@@ -33,6 +35,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// GetAggregateAttestation aggregates all attestations matching the given attestation data root and slot, returning the aggregated result.
|
||||
@@ -630,3 +634,548 @@ func (s *Server) PrepareBeaconProposer(w http.ResponseWriter, r *http.Request) {
|
||||
"validatorIndices": validatorIndices,
|
||||
}).Info("Updated fee recipient addresses")
|
||||
}
|
||||
|
||||
// GetAttesterDuties requests the beacon node to provide a set of attestation duties,
|
||||
// which should be performed by validators, for a particular epoch.
|
||||
func (s *Server) GetAttesterDuties(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetAttesterDuties")
|
||||
defer span.End()
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
rawEpoch := mux.Vars(r)["epoch"]
|
||||
requestedEpochUint, valid := shared.ValidateUint(w, "Epoch", rawEpoch)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedEpoch := primitives.Epoch(requestedEpochUint)
|
||||
var indices []string
|
||||
err := json.NewDecoder(r.Body).Decode(&indices)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
requestedValIndices := make([]primitives.ValidatorIndex, len(indices))
|
||||
for i, ix := range indices {
|
||||
valIx, valid := shared.ValidateUint(w, fmt.Sprintf("ValidatorIndices[%d]", i), ix)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedValIndices[i] = primitives.ValidatorIndex(valIx)
|
||||
}
|
||||
|
||||
cs := s.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(cs)
|
||||
nextEpoch := currentEpoch + 1
|
||||
if requestedEpoch > nextEpoch {
|
||||
http2.HandleError(
|
||||
w,
|
||||
fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", requestedEpoch, nextEpoch),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var startSlot primitives.Slot
|
||||
if requestedEpoch == nextEpoch {
|
||||
startSlot, err = slots.EpochStart(currentEpoch)
|
||||
} else {
|
||||
startSlot, err = slots.EpochStart(requestedEpoch)
|
||||
}
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get start slot from epoch %d: %v", requestedEpoch, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
st, err := s.Stater.StateBySlot(ctx, startSlot)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
committeeAssignments, _, err := helpers.CommitteeAssignments(ctx, st, requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not compute committee assignments: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, st, requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get active validator count: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
committeesAtSlot := helpers.SlotCommitteeCount(activeValidatorCount)
|
||||
|
||||
duties := make([]*AttesterDuty, 0, len(requestedValIndices))
|
||||
for _, index := range requestedValIndices {
|
||||
pubkey := st.PubkeyAtIndex(index)
|
||||
var zeroPubkey [fieldparams.BLSPubkeyLength]byte
|
||||
if bytes.Equal(pubkey[:], zeroPubkey[:]) {
|
||||
http2.HandleError(w, fmt.Sprintf("Invalid validator index %d", index), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
committee := committeeAssignments[index]
|
||||
if committee == nil {
|
||||
continue
|
||||
}
|
||||
var valIndexInCommittee int
|
||||
// valIndexInCommittee will be 0 in case we don't get a match. This is a potential false positive,
|
||||
// however it's an impossible condition because every validator must be assigned to a committee.
|
||||
for cIndex, vIndex := range committee.Committee {
|
||||
if vIndex == index {
|
||||
valIndexInCommittee = cIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
duties = append(duties, &AttesterDuty{
|
||||
Pubkey: hexutil.Encode(pubkey[:]),
|
||||
ValidatorIndex: strconv.FormatUint(uint64(index), 10),
|
||||
CommitteeIndex: strconv.FormatUint(uint64(committee.CommitteeIndex), 10),
|
||||
CommitteeLength: strconv.Itoa(len(committee.Committee)),
|
||||
CommitteesAtSlot: strconv.FormatUint(committeesAtSlot, 10),
|
||||
ValidatorCommitteeIndex: strconv.Itoa(valIndexInCommittee),
|
||||
Slot: strconv.FormatUint(uint64(committee.AttesterSlot), 10),
|
||||
})
|
||||
}
|
||||
|
||||
dependentRoot, err := attestationDependentRoot(st, requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get dependent root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := &GetAttesterDutiesResponse{
|
||||
DependentRoot: hexutil.Encode(dependentRoot),
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}
|
||||
http2.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// GetProposerDuties requests beacon node to provide all validators that are scheduled to propose a block in the given epoch.
|
||||
func (s *Server) GetProposerDuties(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetProposerDuties")
|
||||
defer span.End()
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
rawEpoch := mux.Vars(r)["epoch"]
|
||||
requestedEpochUint, valid := shared.ValidateUint(w, "Epoch", rawEpoch)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedEpoch := primitives.Epoch(requestedEpochUint)
|
||||
|
||||
cs := s.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(cs)
|
||||
nextEpoch := currentEpoch + 1
|
||||
var nextEpochLookahead bool
|
||||
if requestedEpoch > nextEpoch {
|
||||
http2.HandleError(
|
||||
w,
|
||||
fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", requestedEpoch, currentEpoch+1),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
} else if requestedEpoch == nextEpoch {
|
||||
// If the request is for the next epoch, we use the current epoch's state to compute duties.
|
||||
requestedEpoch = currentEpoch
|
||||
nextEpochLookahead = true
|
||||
}
|
||||
|
||||
epochStartSlot, err := slots.EpochStart(requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get start slot of epoch %d: %v", requestedEpoch, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err := s.Stater.StateBySlot(ctx, epochStartSlot)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get state for slot %d: %v ", epochStartSlot, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var proposals map[primitives.ValidatorIndex][]primitives.Slot
|
||||
if nextEpochLookahead {
|
||||
_, proposals, err = helpers.CommitteeAssignments(ctx, st, nextEpoch)
|
||||
} else {
|
||||
_, proposals, err = helpers.CommitteeAssignments(ctx, st, requestedEpoch)
|
||||
}
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not compute committee assignments: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
duties := make([]*ProposerDuty, 0)
|
||||
for index, proposalSlots := range proposals {
|
||||
val, err := st.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get validator at index %d: %v", index, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pubkey48 := val.PublicKey()
|
||||
pubkey := pubkey48[:]
|
||||
for _, slot := range proposalSlots {
|
||||
s.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, index, [8]byte{} /* payloadID */, [32]byte{} /* head root */)
|
||||
duties = append(duties, &ProposerDuty{
|
||||
Pubkey: hexutil.Encode(pubkey),
|
||||
ValidatorIndex: strconv.FormatUint(uint64(index), 10),
|
||||
Slot: strconv.FormatUint(uint64(slot), 10),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
s.ProposerSlotIndexCache.PrunePayloadIDs(epochStartSlot)
|
||||
|
||||
dependentRoot, err := proposalDependentRoot(st, requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get dependent root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !sortProposerDuties(w, duties) {
|
||||
return
|
||||
}
|
||||
|
||||
resp := &GetProposerDutiesResponse{
|
||||
DependentRoot: hexutil.Encode(dependentRoot),
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetSyncCommitteeDuties provides a set of sync committee duties for a particular epoch.
|
||||
//
|
||||
// The logic for calculating epoch validity comes from https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/getSyncCommitteeDuties
|
||||
// where `epoch` is described as `epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD <= current_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + 1`.
|
||||
//
|
||||
// Algorithm:
|
||||
// - Get the last valid epoch. This is the last epoch of the next sync committee period.
|
||||
// - Get the state for the requested epoch. If it's a future epoch from the current sync committee period
|
||||
// or an epoch from the next sync committee period, then get the current state.
|
||||
// - Get the state's current sync committee. If it's an epoch from the next sync committee period, then get the next sync committee.
|
||||
// - Get duties.
|
||||
func (s *Server) GetSyncCommitteeDuties(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetSyncCommitteeDuties")
|
||||
defer span.End()
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
rawEpoch := mux.Vars(r)["epoch"]
|
||||
requestedEpochUint, valid := shared.ValidateUint(w, "Epoch", rawEpoch)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedEpoch := primitives.Epoch(requestedEpochUint)
|
||||
if requestedEpoch < params.BeaconConfig().AltairForkEpoch {
|
||||
http2.HandleError(w, "Sync committees are not supported for Phase0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var indices []string
|
||||
err := json.NewDecoder(r.Body).Decode(&indices)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
requestedValIndices := make([]primitives.ValidatorIndex, len(indices))
|
||||
for i, ix := range indices {
|
||||
valIx, valid := shared.ValidateUint(w, fmt.Sprintf("ValidatorIndices[%d]", i), ix)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedValIndices[i] = primitives.ValidatorIndex(valIx)
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
|
||||
lastValidEpoch := syncCommitteeDutiesLastValidEpoch(currentEpoch)
|
||||
if requestedEpoch > lastValidEpoch {
|
||||
http2.HandleError(w, fmt.Sprintf("Epoch is too far in the future, maximum valid epoch is %d", lastValidEpoch), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
startingEpoch := requestedEpoch
|
||||
if startingEpoch > currentEpoch {
|
||||
startingEpoch = currentEpoch
|
||||
}
|
||||
slot, err := slots.EpochStart(startingEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync committee slot: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err := s.Stater.State(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync committee state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
currentSyncCommitteeFirstEpoch, err := slots.SyncCommitteePeriodStartEpoch(startingEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync committee period start epoch: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
nextSyncCommitteeFirstEpoch := currentSyncCommitteeFirstEpoch + params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
var committee *ethpbalpha.SyncCommittee
|
||||
if requestedEpoch >= nextSyncCommitteeFirstEpoch {
|
||||
committee, err = st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync committee: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
committee, err = st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync committee: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
committeePubkeys := make(map[[fieldparams.BLSPubkeyLength]byte][]string)
|
||||
for j, pubkey := range committee.Pubkeys {
|
||||
pubkey48 := bytesutil.ToBytes48(pubkey)
|
||||
committeePubkeys[pubkey48] = append(committeePubkeys[pubkey48], strconv.FormatUint(uint64(j), 10))
|
||||
}
|
||||
duties, err := syncCommitteeDuties(requestedValIndices, st, committeePubkeys)
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &GetSyncCommitteeDutiesResponse{
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetLiveness requests the beacon node to indicate if a validator has been observed to be live in a given epoch.
|
||||
// The beacon node might detect liveness by observing messages from the validator on the network,
|
||||
// in the beacon chain, from its API or from any other source.
|
||||
// A beacon node SHOULD support the current and previous epoch, however it MAY support earlier epoch.
|
||||
// It is important to note that the values returned by the beacon node are not canonical;
|
||||
// they are best-effort and based upon a subjective view of the network.
|
||||
// A beacon node that was recently started or suffered a network partition may indicate that a validator is not live when it actually is.
|
||||
func (s *Server) GetLiveness(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetLiveness")
|
||||
defer span.End()
|
||||
|
||||
rawEpoch := mux.Vars(r)["epoch"]
|
||||
requestedEpochUint, valid := shared.ValidateUint(w, "Epoch", rawEpoch)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedEpoch := primitives.Epoch(requestedEpochUint)
|
||||
var indices []string
|
||||
err := json.NewDecoder(r.Body).Decode(&indices)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
requestedValIndices := make([]primitives.ValidatorIndex, len(indices))
|
||||
for i, ix := range indices {
|
||||
valIx, valid := shared.ValidateUint(w, fmt.Sprintf("ValidatorIndices[%d]", i), ix)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedValIndices[i] = primitives.ValidatorIndex(valIx)
|
||||
}
|
||||
|
||||
// First we check if the requested epoch is the current epoch.
|
||||
// If it is, then we won't be able to fetch the state at the end of the epoch.
|
||||
// In that case we get participation info from the head state.
|
||||
// We can also use the head state to get participation info for the previous epoch.
|
||||
headSt, err := s.HeadFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
currEpoch := slots.ToEpoch(headSt.Slot())
|
||||
if requestedEpoch > currEpoch {
|
||||
http2.HandleError(w, "Requested epoch cannot be in the future", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var st state.BeaconState
|
||||
var participation []byte
|
||||
if requestedEpoch == currEpoch {
|
||||
st = headSt
|
||||
participation, err = st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get current epoch participation: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if requestedEpoch == currEpoch-1 {
|
||||
st = headSt
|
||||
participation, err = st.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get previous epoch participation: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
epochEnd, err := slots.EpochEnd(requestedEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get requested epoch's end slot: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err = s.Stater.StateBySlot(ctx, epochEnd)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get slot for requested epoch: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
participation, err = st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get current epoch participation: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp := &GetLivenessResponse{
|
||||
Data: make([]*ValidatorLiveness, len(requestedValIndices)),
|
||||
}
|
||||
for i, vi := range requestedValIndices {
|
||||
if vi >= primitives.ValidatorIndex(len(participation)) {
|
||||
http2.HandleError(w, fmt.Sprintf("Validator index %d is invalid", vi), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
resp.Data[i] = &ValidatorLiveness{
|
||||
Index: strconv.FormatUint(uint64(vi), 10),
|
||||
IsLive: participation[vi] != 0,
|
||||
}
|
||||
}
|
||||
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// attestationDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)
|
||||
// or the genesis block root in the case of underflow.
|
||||
func attestationDependentRoot(s state.BeaconState, epoch primitives.Epoch) ([]byte, error) {
|
||||
var dependentRootSlot primitives.Slot
|
||||
if epoch <= 1 {
|
||||
dependentRootSlot = 0
|
||||
} else {
|
||||
prevEpochStartSlot, err := slots.EpochStart(epoch.Sub(1))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err)
|
||||
}
|
||||
dependentRootSlot = prevEpochStartSlot.Sub(1)
|
||||
}
|
||||
root, err := helpers.BlockRootAtSlot(s, dependentRootSlot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// proposalDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)
|
||||
// or the genesis block root in the case of underflow.
|
||||
func proposalDependentRoot(s state.BeaconState, epoch primitives.Epoch) ([]byte, error) {
|
||||
var dependentRootSlot primitives.Slot
|
||||
if epoch == 0 {
|
||||
dependentRootSlot = 0
|
||||
} else {
|
||||
epochStartSlot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err)
|
||||
}
|
||||
dependentRootSlot = epochStartSlot.Sub(1)
|
||||
}
|
||||
root, err := helpers.BlockRootAtSlot(s, dependentRootSlot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func syncCommitteeDutiesLastValidEpoch(currentEpoch primitives.Epoch) primitives.Epoch {
|
||||
currentSyncPeriodIndex := currentEpoch / params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
// Return the last epoch of the next sync committee.
|
||||
// To do this we go two periods ahead to find the first invalid epoch, and then subtract 1.
|
||||
return (currentSyncPeriodIndex+2)*params.BeaconConfig().EpochsPerSyncCommitteePeriod - 1
|
||||
}
|
||||
|
||||
func syncCommitteeDuties(
|
||||
valIndices []primitives.ValidatorIndex,
|
||||
st state.BeaconState,
|
||||
committeePubkeys map[[fieldparams.BLSPubkeyLength]byte][]string,
|
||||
) ([]*SyncCommitteeDuty, error) {
|
||||
duties := make([]*SyncCommitteeDuty, 0)
|
||||
for _, index := range valIndices {
|
||||
duty := &SyncCommitteeDuty{
|
||||
ValidatorIndex: strconv.FormatUint(uint64(index), 10),
|
||||
}
|
||||
valPubkey := st.PubkeyAtIndex(index)
|
||||
var zeroPubkey [fieldparams.BLSPubkeyLength]byte
|
||||
if bytes.Equal(valPubkey[:], zeroPubkey[:]) {
|
||||
return nil, errors.Errorf("Invalid validator index %d", index)
|
||||
}
|
||||
duty.Pubkey = hexutil.Encode(valPubkey[:])
|
||||
indices, ok := committeePubkeys[valPubkey]
|
||||
if ok {
|
||||
duty.ValidatorSyncCommitteeIndices = indices
|
||||
duties = append(duties, duty)
|
||||
}
|
||||
}
|
||||
return duties, nil
|
||||
}
|
||||
|
||||
func sortProposerDuties(w http.ResponseWriter, duties []*ProposerDuty) bool {
|
||||
ok := true
|
||||
sort.Slice(duties, func(i, j int) bool {
|
||||
si, err := strconv.ParseUint(duties[i].Slot, 10, 64)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not parse slot: "+err.Error(), http.StatusInternalServerError)
|
||||
ok = false
|
||||
return false
|
||||
}
|
||||
sj, err := strconv.ParseUint(duties[j].Slot, 10, 64)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not parse slot: "+err.Error(), http.StatusInternalServerError)
|
||||
ok = false
|
||||
return false
|
||||
}
|
||||
return si < sj
|
||||
})
|
||||
return ok
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,45 @@ type ProduceSyncCommitteeContributionResponse struct {
|
||||
Data *shared.SyncCommitteeContribution `json:"data"`
|
||||
}
|
||||
|
||||
type GetAttesterDutiesResponse struct {
|
||||
DependentRoot string `json:"dependent_root"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*AttesterDuty `json:"data"`
|
||||
}
|
||||
|
||||
type AttesterDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
CommitteeIndex string `json:"committee_index"`
|
||||
CommitteeLength string `json:"committee_length"`
|
||||
CommitteesAtSlot string `json:"committees_at_slot"`
|
||||
ValidatorCommitteeIndex string `json:"validator_committee_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
type GetProposerDutiesResponse struct {
|
||||
DependentRoot string `json:"dependent_root"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*ProposerDuty `json:"data"`
|
||||
}
|
||||
|
||||
type ProposerDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
type GetSyncCommitteeDutiesResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*SyncCommitteeDuty `json:"data"`
|
||||
}
|
||||
|
||||
type SyncCommitteeDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
|
||||
}
|
||||
|
||||
// ProduceBlockV3Response is a wrapper json object for the returned block from the ProduceBlockV3 endpoint
|
||||
type ProduceBlockV3Response struct {
|
||||
Version string `json:"version"`
|
||||
@@ -41,3 +80,12 @@ type ProduceBlockV3Response struct {
|
||||
ExecutionPayloadValue string `json:"execution_payload_value"`
|
||||
Data json.RawMessage `json:"data"` // represents the block values based on the version
|
||||
}
|
||||
|
||||
type GetLivenessResponse struct {
|
||||
Data []*ValidatorLiveness `json:"data"`
|
||||
}
|
||||
|
||||
type ValidatorLiveness struct {
|
||||
Index string `json:"index"`
|
||||
IsLive bool `json:"is_live"`
|
||||
}
|
||||
|
||||
@@ -1,291 +1,19 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
rpchelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var errInvalidValIndex = errors.New("invalid validator index")
|
||||
var errParticipation = status.Error(codes.Internal, "Could not obtain epoch participation")
|
||||
|
||||
// GetAttesterDuties requests the beacon node to provide a set of attestation duties,
|
||||
// which should be performed by validators, for a particular epoch.
|
||||
func (vs *Server) GetAttesterDuties(ctx context.Context, req *ethpbv1.AttesterDutiesRequest) (*ethpbv1.AttesterDutiesResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetAttesterDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := vs.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(cs)
|
||||
if req.Epoch > currentEpoch+1 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Request epoch %d can not be greater than next epoch %d", req.Epoch, currentEpoch+1)
|
||||
}
|
||||
|
||||
isOptimistic, err := vs.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check optimistic status: %v", err)
|
||||
}
|
||||
|
||||
var startSlot primitives.Slot
|
||||
if req.Epoch == currentEpoch+1 {
|
||||
startSlot, err = slots.EpochStart(currentEpoch)
|
||||
} else {
|
||||
startSlot, err = slots.EpochStart(req.Epoch)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get start slot from epoch %d: %v", req.Epoch, err)
|
||||
}
|
||||
|
||||
s, err := vs.Stater.StateBySlot(ctx, startSlot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
}
|
||||
|
||||
committeeAssignments, _, err := helpers.CommitteeAssignments(ctx, s, req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err)
|
||||
}
|
||||
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, s, req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
|
||||
}
|
||||
committeesAtSlot := helpers.SlotCommitteeCount(activeValidatorCount)
|
||||
|
||||
duties := make([]*ethpbv1.AttesterDuty, 0, len(req.Index))
|
||||
for _, index := range req.Index {
|
||||
pubkey := s.PubkeyAtIndex(index)
|
||||
var zeroPubkey [fieldparams.BLSPubkeyLength]byte
|
||||
if bytes.Equal(pubkey[:], zeroPubkey[:]) {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid validator index")
|
||||
}
|
||||
committee := committeeAssignments[index]
|
||||
if committee == nil {
|
||||
continue
|
||||
}
|
||||
var valIndexInCommittee primitives.CommitteeIndex
|
||||
// valIndexInCommittee will be 0 in case we don't get a match. This is a potential false positive,
|
||||
// however it's an impossible condition because every validator must be assigned to a committee.
|
||||
for cIndex, vIndex := range committee.Committee {
|
||||
if vIndex == index {
|
||||
valIndexInCommittee = primitives.CommitteeIndex(uint64(cIndex))
|
||||
break
|
||||
}
|
||||
}
|
||||
duties = append(duties, ðpbv1.AttesterDuty{
|
||||
Pubkey: pubkey[:],
|
||||
ValidatorIndex: index,
|
||||
CommitteeIndex: committee.CommitteeIndex,
|
||||
CommitteeLength: uint64(len(committee.Committee)),
|
||||
CommitteesAtSlot: committeesAtSlot,
|
||||
ValidatorCommitteeIndex: valIndexInCommittee,
|
||||
Slot: committee.AttesterSlot,
|
||||
})
|
||||
}
|
||||
|
||||
root, err := attestationDependentRoot(s, req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
|
||||
}
|
||||
|
||||
return ðpbv1.AttesterDutiesResponse{
|
||||
DependentRoot: root,
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetProposerDuties requests beacon node to provide all validators that are scheduled to propose a block in the given epoch.
|
||||
func (vs *Server) GetProposerDuties(ctx context.Context, req *ethpbv1.ProposerDutiesRequest) (*ethpbv1.ProposerDutiesResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetProposerDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isOptimistic, err := vs.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check optimistic status: %v", err)
|
||||
}
|
||||
|
||||
cs := vs.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(cs)
|
||||
nextEpoch := currentEpoch + 1
|
||||
var nextEpochLookahead bool
|
||||
if req.Epoch > nextEpoch {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Request epoch %d can not be greater than next epoch %d", req.Epoch, currentEpoch+1)
|
||||
} else if req.Epoch == nextEpoch {
|
||||
// If the request is for the next epoch, we use the current epoch's state to compute duties.
|
||||
req.Epoch = currentEpoch
|
||||
nextEpochLookahead = true
|
||||
}
|
||||
|
||||
startSlot, err := slots.EpochStart(req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get start slot from epoch %d: %v", req.Epoch, err)
|
||||
}
|
||||
s, err := vs.Stater.StateBySlot(ctx, startSlot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
}
|
||||
|
||||
var proposals map[primitives.ValidatorIndex][]primitives.Slot
|
||||
if nextEpochLookahead {
|
||||
_, proposals, err = helpers.CommitteeAssignments(ctx, s, nextEpoch)
|
||||
} else {
|
||||
_, proposals, err = helpers.CommitteeAssignments(ctx, s, req.Epoch)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err)
|
||||
}
|
||||
|
||||
duties := make([]*ethpbv1.ProposerDuty, 0)
|
||||
for index, ss := range proposals {
|
||||
val, err := s.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err)
|
||||
}
|
||||
pubkey48 := val.PublicKey()
|
||||
pubkey := pubkey48[:]
|
||||
for _, s := range ss {
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(s, index, [8]byte{} /* payloadID */, [32]byte{} /* head root */)
|
||||
duties = append(duties, ðpbv1.ProposerDuty{
|
||||
Pubkey: pubkey,
|
||||
ValidatorIndex: index,
|
||||
Slot: s,
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Slice(duties, func(i, j int) bool {
|
||||
return duties[i].Slot < duties[j].Slot
|
||||
})
|
||||
|
||||
root, err := proposalDependentRoot(s, req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
|
||||
}
|
||||
|
||||
vs.ProposerSlotIndexCache.PrunePayloadIDs(startSlot)
|
||||
|
||||
return ðpbv1.ProposerDutiesResponse{
|
||||
DependentRoot: root,
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSyncCommitteeDuties provides a set of sync committee duties for a particular epoch.
|
||||
//
|
||||
// The logic for calculating epoch validity comes from https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/getSyncCommitteeDuties
|
||||
// where `epoch` is described as `epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD <= current_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + 1`.
|
||||
//
|
||||
// Algorithm:
|
||||
// - Get the last valid epoch. This is the last epoch of the next sync committee period.
|
||||
// - Get the state for the requested epoch. If it's a future epoch from the current sync committee period
|
||||
// or an epoch from the next sync committee period, then get the current state.
|
||||
// - Get the state's current sync committee. If it's an epoch from the next sync committee period, then get the next sync committee.
|
||||
// - Get duties.
|
||||
func (vs *Server) GetSyncCommitteeDuties(ctx context.Context, req *ethpbv2.SyncCommitteeDutiesRequest) (*ethpbv2.SyncCommitteeDutiesResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetSyncCommitteeDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(vs.TimeFetcher.CurrentSlot())
|
||||
lastValidEpoch := syncCommitteeDutiesLastValidEpoch(currentEpoch)
|
||||
if req.Epoch > lastValidEpoch {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Epoch is too far in the future. Maximum valid epoch is %v.", lastValidEpoch)
|
||||
}
|
||||
|
||||
requestedEpoch := req.Epoch
|
||||
if requestedEpoch > currentEpoch {
|
||||
requestedEpoch = currentEpoch
|
||||
}
|
||||
slot, err := slots.EpochStart(requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get sync committee slot: %v", err)
|
||||
}
|
||||
st, err := vs.Stater.State(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get sync committee state: %v", err)
|
||||
}
|
||||
|
||||
currentSyncCommitteeFirstEpoch, err := slots.SyncCommitteePeriodStartEpoch(requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Could not get sync committee period start epoch: %v.", err)
|
||||
}
|
||||
nextSyncCommitteeFirstEpoch := currentSyncCommitteeFirstEpoch + params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
var committee *ethpbalpha.SyncCommittee
|
||||
if req.Epoch >= nextSyncCommitteeFirstEpoch {
|
||||
committee, err = st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get sync committee: %v", err)
|
||||
}
|
||||
} else {
|
||||
committee, err = st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get sync committee: %v", err)
|
||||
}
|
||||
}
|
||||
committeePubkeys := make(map[[fieldparams.BLSPubkeyLength]byte][]uint64)
|
||||
for j, pubkey := range committee.Pubkeys {
|
||||
pubkey48 := bytesutil.ToBytes48(pubkey)
|
||||
committeePubkeys[pubkey48] = append(committeePubkeys[pubkey48], uint64(j))
|
||||
}
|
||||
|
||||
duties, err := syncCommitteeDuties(req.Index, st, committeePubkeys)
|
||||
if errors.Is(err, errInvalidValIndex) {
|
||||
return nil, status.Error(codes.InvalidArgument, "Invalid validator index")
|
||||
} else if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get duties: %v", err)
|
||||
}
|
||||
|
||||
isOptimistic, err := rpchelpers.IsOptimistic(
|
||||
ctx,
|
||||
[]byte(strconv.FormatUint(uint64(slot), 10)),
|
||||
vs.OptimisticModeFetcher,
|
||||
vs.Stater,
|
||||
vs.ChainInfoFetcher,
|
||||
vs.BeaconDB,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
|
||||
return ðpbv2.SyncCommitteeDutiesResponse{
|
||||
Data: duties,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ProduceBlockV2 requests the beacon node to produce a valid unsigned beacon block, which can then be signed by a proposer and submitted.
|
||||
// By definition `/eth/v2/validator/blocks/{slot}`, does not produce block using mev-boost and relayer network.
|
||||
// The following endpoint states that the returned object is a BeaconBlock, not a BlindedBeaconBlock. As such, the block must return a full ExecutionPayload:
|
||||
@@ -779,146 +507,3 @@ func (vs *Server) ProduceBlindedBlockSSZ(ctx context.Context, req *ethpbv1.Produ
|
||||
}
|
||||
return nil, status.Error(codes.InvalidArgument, "Unsupported block type")
|
||||
}
|
||||
|
||||
// GetLiveness requests the beacon node to indicate if a validator has been observed to be live in a given epoch.
|
||||
// The beacon node might detect liveness by observing messages from the validator on the network,
|
||||
// in the beacon chain, from its API or from any other source.
|
||||
// A beacon node SHOULD support the current and previous epoch, however it MAY support earlier epoch.
|
||||
// It is important to note that the values returned by the beacon node are not canonical;
|
||||
// they are best-effort and based upon a subjective view of the network.
|
||||
// A beacon node that was recently started or suffered a network partition may indicate that a validator is not live when it actually is.
|
||||
func (vs *Server) GetLiveness(ctx context.Context, req *ethpbv2.GetLivenessRequest) (*ethpbv2.GetLivenessResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetLiveness")
|
||||
defer span.End()
|
||||
|
||||
var participation []byte
|
||||
|
||||
// First we check if the requested epoch is the current epoch.
|
||||
// If it is, then we won't be able to fetch the state at the end of the epoch.
|
||||
// In that case we get participation info from the head state.
|
||||
// We can also use the head state to get participation info for the previous epoch.
|
||||
headSt, err := vs.HeadFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get head state")
|
||||
}
|
||||
currEpoch := slots.ToEpoch(headSt.Slot())
|
||||
if req.Epoch > currEpoch {
|
||||
return nil, status.Error(codes.InvalidArgument, "Requested epoch cannot be in the future")
|
||||
}
|
||||
|
||||
var st state.BeaconState
|
||||
if req.Epoch == currEpoch {
|
||||
st = headSt
|
||||
participation, err = st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, errParticipation
|
||||
}
|
||||
} else if req.Epoch == currEpoch-1 {
|
||||
st = headSt
|
||||
participation, err = st.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, errParticipation
|
||||
}
|
||||
} else {
|
||||
epochEnd, err := slots.EpochEnd(req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get requested epoch's end slot")
|
||||
}
|
||||
st, err = vs.Stater.StateBySlot(ctx, epochEnd)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get slot for requested epoch")
|
||||
}
|
||||
participation, err = st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, errParticipation
|
||||
}
|
||||
}
|
||||
|
||||
resp := ðpbv2.GetLivenessResponse{
|
||||
Data: make([]*ethpbv2.GetLivenessResponse_Liveness, len(req.Index)),
|
||||
}
|
||||
for i, vi := range req.Index {
|
||||
if vi >= primitives.ValidatorIndex(len(participation)) {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Validator index %d is invalid", vi)
|
||||
}
|
||||
resp.Data[i] = ðpbv2.GetLivenessResponse_Liveness{
|
||||
Index: vi,
|
||||
IsLive: participation[vi] != 0,
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// attestationDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)
|
||||
// or the genesis block root in the case of underflow.
|
||||
func attestationDependentRoot(s state.BeaconState, epoch primitives.Epoch) ([]byte, error) {
|
||||
var dependentRootSlot primitives.Slot
|
||||
if epoch <= 1 {
|
||||
dependentRootSlot = 0
|
||||
} else {
|
||||
prevEpochStartSlot, err := slots.EpochStart(epoch.Sub(1))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err)
|
||||
}
|
||||
dependentRootSlot = prevEpochStartSlot.Sub(1)
|
||||
}
|
||||
root, err := helpers.BlockRootAtSlot(s, dependentRootSlot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// proposalDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)
|
||||
// or the genesis block root in the case of underflow.
|
||||
func proposalDependentRoot(s state.BeaconState, epoch primitives.Epoch) ([]byte, error) {
|
||||
var dependentRootSlot primitives.Slot
|
||||
if epoch == 0 {
|
||||
dependentRootSlot = 0
|
||||
} else {
|
||||
epochStartSlot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain epoch's start slot: %v", err)
|
||||
}
|
||||
dependentRootSlot = epochStartSlot.Sub(1)
|
||||
}
|
||||
root, err := helpers.BlockRootAtSlot(s, dependentRootSlot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func syncCommitteeDutiesLastValidEpoch(currentEpoch primitives.Epoch) primitives.Epoch {
|
||||
currentSyncPeriodIndex := currentEpoch / params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
// Return the last epoch of the next sync committee.
|
||||
// To do this we go two periods ahead to find the first invalid epoch, and then subtract 1.
|
||||
return (currentSyncPeriodIndex+2)*params.BeaconConfig().EpochsPerSyncCommitteePeriod - 1
|
||||
}
|
||||
|
||||
func syncCommitteeDuties(
|
||||
valIndices []primitives.ValidatorIndex,
|
||||
st state.BeaconState,
|
||||
committeePubkeys map[[fieldparams.BLSPubkeyLength]byte][]uint64,
|
||||
) ([]*ethpbv2.SyncCommitteeDuty, error) {
|
||||
duties := make([]*ethpbv2.SyncCommitteeDuty, 0)
|
||||
for _, index := range valIndices {
|
||||
duty := ðpbv2.SyncCommitteeDuty{
|
||||
ValidatorIndex: index,
|
||||
}
|
||||
valPubkey48 := st.PubkeyAtIndex(index)
|
||||
var zeroPubkey [fieldparams.BLSPubkeyLength]byte
|
||||
if bytes.Equal(valPubkey48[:], zeroPubkey[:]) {
|
||||
return nil, errInvalidValIndex
|
||||
}
|
||||
valPubkey := valPubkey48[:]
|
||||
duty.Pubkey = valPubkey
|
||||
indices, ok := committeePubkeys[valPubkey48]
|
||||
if ok {
|
||||
duty.ValidatorSyncCommitteeIndices = indices
|
||||
duties = append(duties, duty)
|
||||
}
|
||||
}
|
||||
return duties, nil
|
||||
}
|
||||
|
||||
@@ -2,24 +2,14 @@ package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockChain "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
builderTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/builder/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
dbutil "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
@@ -28,679 +18,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/mock"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func TestGetAttesterDuties(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
ctx := context.Background()
|
||||
genesis := util.NewBeaconBlock()
|
||||
depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
|
||||
deposits, _, err := util.DeterministicDepositsAndKeys(depChainStart)
|
||||
require.NoError(t, err)
|
||||
eth1Data, err := util.DeterministicEth1Data(len(deposits))
|
||||
require.NoError(t, err)
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
// Set state to non-epoch start slot.
|
||||
require.NoError(t, bs.SetSlot(5))
|
||||
genesisRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
roots := make([][]byte, fieldparams.BlockRootsLength)
|
||||
roots[0] = genesisRoot[:]
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
db := dbutil.SetupDB(t)
|
||||
|
||||
// Deactivate last validator.
|
||||
vals := bs.Validators()
|
||||
vals[len(vals)-1].ExitEpoch = 0
|
||||
require.NoError(t, bs.SetValidators(vals))
|
||||
|
||||
pubKeys := make([][]byte, len(deposits))
|
||||
for i := 0; i < len(deposits); i++ {
|
||||
pubKeys[i] = deposits[i].Data.PublicKey
|
||||
}
|
||||
|
||||
// nextEpochState must not be used for committee calculations when requesting next epoch
|
||||
nextEpochState := bs.Copy()
|
||||
require.NoError(t, nextEpochState.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, nextEpochState.SetValidators(vals[:512]))
|
||||
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
0: bs,
|
||||
params.BeaconConfig().SlotsPerEpoch: nextEpochState,
|
||||
},
|
||||
},
|
||||
TimeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: chain,
|
||||
}
|
||||
|
||||
t.Run("Single validator", func(t *testing.T) {
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0},
|
||||
}
|
||||
resp, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, genesisRoot[:], resp.DependentRoot)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.Equal(t, primitives.CommitteeIndex(1), duty.CommitteeIndex)
|
||||
assert.Equal(t, primitives.Slot(0), duty.Slot)
|
||||
assert.Equal(t, primitives.ValidatorIndex(0), duty.ValidatorIndex)
|
||||
assert.DeepEqual(t, pubKeys[0], duty.Pubkey)
|
||||
assert.Equal(t, uint64(171), duty.CommitteeLength)
|
||||
assert.Equal(t, uint64(3), duty.CommitteesAtSlot)
|
||||
assert.Equal(t, primitives.CommitteeIndex(80), duty.ValidatorCommitteeIndex)
|
||||
})
|
||||
|
||||
t.Run("Multiple validators", func(t *testing.T) {
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0, 1},
|
||||
}
|
||||
resp, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
})
|
||||
|
||||
t.Run("Next epoch", func(t *testing.T) {
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: slots.ToEpoch(bs.Slot()) + 1,
|
||||
Index: []primitives.ValidatorIndex{0},
|
||||
}
|
||||
resp, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, genesisRoot[:], resp.DependentRoot)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.Equal(t, primitives.CommitteeIndex(0), duty.CommitteeIndex)
|
||||
assert.Equal(t, primitives.Slot(62), duty.Slot)
|
||||
assert.Equal(t, primitives.ValidatorIndex(0), duty.ValidatorIndex)
|
||||
assert.DeepEqual(t, pubKeys[0], duty.Pubkey)
|
||||
assert.Equal(t, uint64(170), duty.CommitteeLength)
|
||||
assert.Equal(t, uint64(3), duty.CommitteesAtSlot)
|
||||
assert.Equal(t, primitives.CommitteeIndex(110), duty.ValidatorCommitteeIndex)
|
||||
})
|
||||
|
||||
t.Run("Epoch out of bound", func(t *testing.T) {
|
||||
currentEpoch := slots.ToEpoch(bs.Slot())
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: currentEpoch + 2,
|
||||
Index: []primitives.ValidatorIndex{0},
|
||||
}
|
||||
_, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NotNil(t, err)
|
||||
assert.ErrorContains(t, fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", currentEpoch+2, currentEpoch+1), err)
|
||||
})
|
||||
|
||||
t.Run("Validator index out of bound", func(t *testing.T) {
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{primitives.ValidatorIndex(len(pubKeys))},
|
||||
}
|
||||
_, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NotNil(t, err)
|
||||
assert.ErrorContains(t, "Invalid validator index", err)
|
||||
})
|
||||
|
||||
t.Run("Inactive validator - no duties", func(t *testing.T) {
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{primitives.ValidatorIndex(len(pubKeys) - 1)},
|
||||
}
|
||||
resp, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
})
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
blk.Block.Slot = 31
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot, Optimistic: true,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{0: bs}},
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
req := ðpbv1.AttesterDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0},
|
||||
}
|
||||
resp, err := vs.GetAttesterDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAttesterDuties_SyncNotReady(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
chainService := &mockChain.ChainService{State: st}
|
||||
vs := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
}
|
||||
_, err = vs.GetAttesterDuties(context.Background(), ðpbv1.AttesterDutiesRequest{})
|
||||
assert.ErrorContains(t, "Syncing to latest head, not ready to respond", err)
|
||||
}
|
||||
|
||||
func TestGetProposerDuties(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
ctx := context.Background()
|
||||
genesis := util.NewBeaconBlock()
|
||||
depChainStart := params.BeaconConfig().MinGenesisActiveValidatorCount
|
||||
deposits, _, err := util.DeterministicDepositsAndKeys(depChainStart)
|
||||
require.NoError(t, err)
|
||||
eth1Data, err := util.DeterministicEth1Data(len(deposits))
|
||||
require.NoError(t, err)
|
||||
genesisRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
roots := make([][]byte, fieldparams.BlockRootsLength)
|
||||
roots[0] = genesisRoot[:]
|
||||
// We DON'T WANT this root to be returned when testing the next epoch
|
||||
roots[31] = []byte("next_epoch_dependent_root")
|
||||
db := dbutil.SetupDB(t)
|
||||
|
||||
pubKeys := make([][]byte, len(deposits))
|
||||
for i := 0; i < len(deposits); i++ {
|
||||
pubKeys[i] = deposits[i].Data.PublicKey
|
||||
}
|
||||
|
||||
t.Run("Ok", func(t *testing.T) {
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{0: bs}},
|
||||
HeadFetcher: chain,
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
|
||||
req := ðpbv1.ProposerDutiesRequest{
|
||||
Epoch: 0,
|
||||
}
|
||||
resp, err := vs.GetProposerDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, genesisRoot[:], resp.DependentRoot)
|
||||
assert.Equal(t, 31, len(resp.Data))
|
||||
// We expect a proposer duty for slot 11.
|
||||
var expectedDuty *ethpbv1.ProposerDuty
|
||||
for _, duty := range resp.Data {
|
||||
if duty.Slot == 11 {
|
||||
expectedDuty = duty
|
||||
}
|
||||
}
|
||||
vid, _, has := vs.ProposerSlotIndexCache.GetProposerPayloadIDs(11, [32]byte{})
|
||||
require.Equal(t, true, has)
|
||||
require.Equal(t, primitives.ValidatorIndex(12289), vid)
|
||||
require.NotNil(t, expectedDuty, "Expected duty for slot 11 not found")
|
||||
assert.Equal(t, primitives.ValidatorIndex(12289), expectedDuty.ValidatorIndex)
|
||||
assert.DeepEqual(t, pubKeys[12289], expectedDuty.Pubkey)
|
||||
})
|
||||
|
||||
t.Run("Next epoch", func(t *testing.T) {
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{0: bs}},
|
||||
HeadFetcher: chain,
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
|
||||
req := ðpbv1.ProposerDutiesRequest{
|
||||
Epoch: 1,
|
||||
}
|
||||
resp, err := vs.GetProposerDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, bytesutil.PadTo(genesisRoot[:], 32), resp.DependentRoot)
|
||||
assert.Equal(t, 32, len(resp.Data))
|
||||
// We expect a proposer duty for slot 43.
|
||||
var expectedDuty *ethpbv1.ProposerDuty
|
||||
for _, duty := range resp.Data {
|
||||
if duty.Slot == 43 {
|
||||
expectedDuty = duty
|
||||
}
|
||||
}
|
||||
vid, _, has := vs.ProposerSlotIndexCache.GetProposerPayloadIDs(43, [32]byte{})
|
||||
require.Equal(t, true, has)
|
||||
require.Equal(t, primitives.ValidatorIndex(1360), vid)
|
||||
require.NotNil(t, expectedDuty, "Expected duty for slot 43 not found")
|
||||
assert.Equal(t, primitives.ValidatorIndex(1360), expectedDuty.ValidatorIndex)
|
||||
assert.DeepEqual(t, pubKeys[1360], expectedDuty.Pubkey)
|
||||
})
|
||||
|
||||
t.Run("Prune payload ID cache ok", func(t *testing.T) {
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
chainSlot := params.BeaconConfig().SlotsPerEpoch
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{params.BeaconConfig().SlotsPerEpoch: bs}},
|
||||
HeadFetcher: chain,
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
|
||||
req := ðpbv1.ProposerDutiesRequest{
|
||||
Epoch: 1,
|
||||
}
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(1, 1, [8]byte{1}, [32]byte{2})
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(31, 2, [8]byte{2}, [32]byte{3})
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(32, 4309, [8]byte{3}, [32]byte{4})
|
||||
|
||||
_, err = vs.GetProposerDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
vid, _, has := vs.ProposerSlotIndexCache.GetProposerPayloadIDs(1, [32]byte{})
|
||||
require.Equal(t, false, has)
|
||||
require.Equal(t, primitives.ValidatorIndex(0), vid)
|
||||
vid, _, has = vs.ProposerSlotIndexCache.GetProposerPayloadIDs(2, [32]byte{})
|
||||
require.Equal(t, false, has)
|
||||
require.Equal(t, primitives.ValidatorIndex(0), vid)
|
||||
vid, _, has = vs.ProposerSlotIndexCache.GetProposerPayloadIDs(32, [32]byte{})
|
||||
require.Equal(t, true, has)
|
||||
require.Equal(t, primitives.ValidatorIndex(10565), vid)
|
||||
})
|
||||
|
||||
t.Run("Epoch out of bound", func(t *testing.T) {
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
// Set state to non-epoch start slot.
|
||||
require.NoError(t, bs.SetSlot(5))
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{0: bs}},
|
||||
HeadFetcher: chain,
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(bs.Slot())
|
||||
req := ðpbv1.ProposerDutiesRequest{
|
||||
Epoch: currentEpoch + 2,
|
||||
}
|
||||
_, err = vs.GetProposerDuties(ctx, req)
|
||||
require.NotNil(t, err)
|
||||
assert.ErrorContains(t, fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", currentEpoch+2, currentEpoch+1), err)
|
||||
})
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data)
|
||||
require.NoError(t, err, "Could not set up genesis state")
|
||||
// Set state to non-epoch start slot.
|
||||
require.NoError(t, bs.SetSlot(5))
|
||||
require.NoError(t, bs.SetBlockRoots(roots))
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
blk.Block.Slot = 31
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainSlot := primitives.Slot(0)
|
||||
chain := &mockChain.ChainService{
|
||||
State: bs, Root: genesisRoot[:], Slot: &chainSlot, Optimistic: true,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{0: bs}},
|
||||
HeadFetcher: chain,
|
||||
TimeFetcher: chain,
|
||||
OptimisticModeFetcher: chain,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
req := ðpbv1.ProposerDutiesRequest{
|
||||
Epoch: 0,
|
||||
}
|
||||
resp, err := vs.GetProposerDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProposerDuties_SyncNotReady(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
chainService := &mockChain.ChainService{State: st}
|
||||
vs := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
}
|
||||
_, err = vs.GetProposerDuties(context.Background(), ðpbv1.ProposerDutiesRequest{})
|
||||
assert.ErrorContains(t, "Syncing to latest head, not ready to respond", err)
|
||||
}
|
||||
|
||||
func TestGetSyncCommitteeDuties(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
ctx := context.Background()
|
||||
genesisTime := time.Now()
|
||||
numVals := uint64(11)
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, numVals)
|
||||
require.NoError(t, st.SetGenesisTime(uint64(genesisTime.Unix())))
|
||||
vals := st.Validators()
|
||||
currCommittee := ðpbalpha.SyncCommittee{}
|
||||
for i := 0; i < 5; i++ {
|
||||
currCommittee.Pubkeys = append(currCommittee.Pubkeys, vals[i].PublicKey)
|
||||
currCommittee.AggregatePubkey = make([]byte, 48)
|
||||
}
|
||||
// add one public key twice - this is needed for one of the test cases
|
||||
currCommittee.Pubkeys = append(currCommittee.Pubkeys, vals[0].PublicKey)
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(currCommittee))
|
||||
nextCommittee := ðpbalpha.SyncCommittee{}
|
||||
for i := 5; i < 10; i++ {
|
||||
nextCommittee.Pubkeys = append(nextCommittee.Pubkeys, vals[i].PublicKey)
|
||||
nextCommittee.AggregatePubkey = make([]byte, 48)
|
||||
|
||||
}
|
||||
require.NoError(t, st.SetNextSyncCommittee(nextCommittee))
|
||||
|
||||
mockChainService := &mockChain.ChainService{Genesis: genesisTime}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{BeaconState: st},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: mockChainService,
|
||||
HeadFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
}
|
||||
|
||||
t.Run("Single validator", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{1},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.DeepEqual(t, vals[1].PublicKey, duty.Pubkey)
|
||||
assert.Equal(t, primitives.ValidatorIndex(1), duty.ValidatorIndex)
|
||||
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
|
||||
assert.Equal(t, uint64(1), duty.ValidatorSyncCommitteeIndices[0])
|
||||
})
|
||||
|
||||
t.Run("Epoch not at period start", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 1,
|
||||
Index: []primitives.ValidatorIndex{1},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.DeepEqual(t, vals[1].PublicKey, duty.Pubkey)
|
||||
assert.Equal(t, primitives.ValidatorIndex(1), duty.ValidatorIndex)
|
||||
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
|
||||
assert.Equal(t, uint64(1), duty.ValidatorSyncCommitteeIndices[0])
|
||||
})
|
||||
|
||||
t.Run("Multiple validators", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{1, 2},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
})
|
||||
|
||||
t.Run("Validator without duty not returned", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{1, 10},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
assert.Equal(t, primitives.ValidatorIndex(1), resp.Data[0].ValidatorIndex)
|
||||
})
|
||||
|
||||
t.Run("Multiple indices for validator", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
duty := resp.Data[0]
|
||||
require.Equal(t, 2, len(duty.ValidatorSyncCommitteeIndices))
|
||||
assert.DeepEqual(t, []uint64{0, 5}, duty.ValidatorSyncCommitteeIndices)
|
||||
})
|
||||
|
||||
t.Run("Validator index out of bound", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{primitives.ValidatorIndex(numVals)},
|
||||
}
|
||||
_, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NotNil(t, err)
|
||||
assert.ErrorContains(t, "Invalid validator index", err)
|
||||
})
|
||||
|
||||
t.Run("next sync committee period", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod,
|
||||
Index: []primitives.ValidatorIndex{5},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.DeepEqual(t, vals[5].PublicKey, duty.Pubkey)
|
||||
assert.Equal(t, primitives.ValidatorIndex(5), duty.ValidatorIndex)
|
||||
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
|
||||
assert.Equal(t, uint64(0), duty.ValidatorSyncCommitteeIndices[0])
|
||||
})
|
||||
|
||||
t.Run("epoch too far in the future", func(t *testing.T) {
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod * 2,
|
||||
Index: []primitives.ValidatorIndex{5},
|
||||
}
|
||||
_, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NotNil(t, err)
|
||||
assert.ErrorContains(t, "Epoch is too far in the future", err)
|
||||
})
|
||||
|
||||
t.Run("correct sync committee is fetched", func(t *testing.T) {
|
||||
// in this test we swap validators in the current and next sync committee inside the new state
|
||||
|
||||
newSyncPeriodStartSlot := primitives.Slot(uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod) * uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||
newSyncPeriodSt, _ := util.DeterministicGenesisStateAltair(t, numVals)
|
||||
require.NoError(t, newSyncPeriodSt.SetSlot(newSyncPeriodStartSlot))
|
||||
require.NoError(t, newSyncPeriodSt.SetGenesisTime(uint64(genesisTime.Unix())))
|
||||
vals := newSyncPeriodSt.Validators()
|
||||
currCommittee := ðpbalpha.SyncCommittee{}
|
||||
for i := 5; i < 10; i++ {
|
||||
currCommittee.Pubkeys = append(currCommittee.Pubkeys, vals[i].PublicKey)
|
||||
currCommittee.AggregatePubkey = make([]byte, 48)
|
||||
}
|
||||
require.NoError(t, newSyncPeriodSt.SetCurrentSyncCommittee(currCommittee))
|
||||
nextCommittee := ðpbalpha.SyncCommittee{}
|
||||
for i := 0; i < 5; i++ {
|
||||
nextCommittee.Pubkeys = append(nextCommittee.Pubkeys, vals[i].PublicKey)
|
||||
nextCommittee.AggregatePubkey = make([]byte, 48)
|
||||
|
||||
}
|
||||
require.NoError(t, newSyncPeriodSt.SetNextSyncCommittee(nextCommittee))
|
||||
|
||||
stateFetchFn := func(slot primitives.Slot) state.BeaconState {
|
||||
if slot < newSyncPeriodStartSlot {
|
||||
return st
|
||||
} else {
|
||||
return newSyncPeriodSt
|
||||
}
|
||||
}
|
||||
mockChainService := &mockChain.ChainService{Genesis: genesisTime, Slot: &newSyncPeriodStartSlot}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{BeaconState: stateFetchFn(newSyncPeriodStartSlot)},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: mockChainService,
|
||||
HeadFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
}
|
||||
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod,
|
||||
Index: []primitives.ValidatorIndex{8},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
duty := resp.Data[0]
|
||||
assert.DeepEqual(t, vals[8].PublicKey, duty.Pubkey)
|
||||
assert.Equal(t, primitives.ValidatorIndex(8), duty.ValidatorIndex)
|
||||
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
|
||||
assert.Equal(t, uint64(3), duty.ValidatorSyncCommitteeIndices[0])
|
||||
})
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
db := dbutil.SetupDB(t)
|
||||
require.NoError(t, db.SaveStateSummary(ctx, ðpbalpha.StateSummary{Slot: 0, Root: []byte("root")}))
|
||||
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, ðpbalpha.Checkpoint{Epoch: 0, Root: []byte("root")}))
|
||||
|
||||
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))
|
||||
|
||||
slot, err := slots.EpochStart(1)
|
||||
require.NoError(t, err)
|
||||
|
||||
state, err := util.NewBeaconStateBellatrix()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, state.SetSlot(slot))
|
||||
|
||||
mockChainService := &mockChain.ChainService{
|
||||
Genesis: genesisTime,
|
||||
Optimistic: true,
|
||||
Slot: &slot,
|
||||
FinalizedCheckPoint: ðpbalpha.Checkpoint{
|
||||
Root: root[:],
|
||||
Epoch: 1,
|
||||
},
|
||||
State: state,
|
||||
}
|
||||
vs := &Server{
|
||||
Stater: &testutil.MockStater{BeaconState: st},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: mockChainService,
|
||||
HeadFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
ChainInfoFetcher: mockChainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
req := ðpbv2.SyncCommitteeDutiesRequest{
|
||||
Epoch: 1,
|
||||
Index: []primitives.ValidatorIndex{1},
|
||||
}
|
||||
resp, err := vs.GetSyncCommitteeDuties(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSyncCommitteeDuties_SyncNotReady(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
chainService := &mockChain.ChainService{State: st}
|
||||
vs := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: true},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
}
|
||||
_, err = vs.GetSyncCommitteeDuties(context.Background(), ðpbv2.SyncCommitteeDutiesRequest{})
|
||||
assert.ErrorContains(t, "Syncing to latest head, not ready to respond", err)
|
||||
}
|
||||
|
||||
func TestSyncCommitteeDutiesLastValidEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
|
||||
t.Run("first epoch of current period", func(t *testing.T) {
|
||||
assert.Equal(t, params.BeaconConfig().EpochsPerSyncCommitteePeriod*2-1, syncCommitteeDutiesLastValidEpoch(0))
|
||||
})
|
||||
t.Run("last epoch of current period", func(t *testing.T) {
|
||||
assert.Equal(
|
||||
t,
|
||||
params.BeaconConfig().EpochsPerSyncCommitteePeriod*2-1,
|
||||
syncCommitteeDutiesLastValidEpoch(params.BeaconConfig().EpochsPerSyncCommitteePeriod-1),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProduceBlockV2(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
ctx := context.Background()
|
||||
@@ -1023,7 +342,7 @@ func TestProduceBlockV2SSZ(t *testing.T) {
|
||||
genericBlock := ðpbalpha.GenericBeaconBlock{
|
||||
Block: ðpbalpha.GenericBeaconBlock_BlindedDeneb{
|
||||
BlindedDeneb: ðpbalpha.BlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: blk.Block,
|
||||
Block: blk.Message,
|
||||
Blobs: blobs,
|
||||
},
|
||||
},
|
||||
@@ -1186,7 +505,7 @@ func TestProduceBlindedBlock(t *testing.T) {
|
||||
genericBlock := ðpbalpha.GenericBeaconBlock{
|
||||
Block: ðpbalpha.GenericBeaconBlock_BlindedDeneb{
|
||||
BlindedDeneb: ðpbalpha.BlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: blk.Block,
|
||||
Block: blk.Message,
|
||||
Blobs: blobs,
|
||||
},
|
||||
},
|
||||
@@ -1396,7 +715,7 @@ func TestProduceBlindedBlockSSZ(t *testing.T) {
|
||||
genericBlock := ðpbalpha.GenericBeaconBlock{
|
||||
Block: ðpbalpha.GenericBeaconBlock_BlindedDeneb{
|
||||
BlindedDeneb: ðpbalpha.BlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: blk.Block,
|
||||
Block: blk.Message,
|
||||
Blobs: blobs,
|
||||
},
|
||||
},
|
||||
@@ -1484,82 +803,3 @@ func TestProduceBlindedBlockSSZ(t *testing.T) {
|
||||
require.ErrorContains(t, "Syncing to latest head", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetLiveness(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup:
|
||||
// Epoch 0 - both validators not live
|
||||
// Epoch 1 - validator with index 1 is live
|
||||
// Epoch 2 - validator with index 0 is live
|
||||
oldSt, err := util.NewBeaconStateBellatrix()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, oldSt.AppendCurrentParticipationBits(0))
|
||||
require.NoError(t, oldSt.AppendCurrentParticipationBits(0))
|
||||
headSt, err := util.NewBeaconStateBellatrix()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headSt.SetSlot(params.BeaconConfig().SlotsPerEpoch*2))
|
||||
require.NoError(t, headSt.AppendPreviousParticipationBits(0))
|
||||
require.NoError(t, headSt.AppendPreviousParticipationBits(1))
|
||||
require.NoError(t, headSt.AppendCurrentParticipationBits(1))
|
||||
require.NoError(t, headSt.AppendCurrentParticipationBits(0))
|
||||
|
||||
server := &Server{
|
||||
HeadFetcher: &mockChain.ChainService{State: headSt},
|
||||
Stater: &testutil.MockStater{
|
||||
// We configure states for last slots of an epoch
|
||||
StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
params.BeaconConfig().SlotsPerEpoch - 1: oldSt,
|
||||
params.BeaconConfig().SlotsPerEpoch*3 - 1: headSt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("old epoch", func(t *testing.T) {
|
||||
resp, err := server.GetLiveness(ctx, ðpbv2.GetLivenessRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0, 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
data0 := resp.Data[0]
|
||||
data1 := resp.Data[1]
|
||||
assert.Equal(t, true, (data0.Index == 0 && !data0.IsLive) || (data0.Index == 1 && !data0.IsLive))
|
||||
assert.Equal(t, true, (data1.Index == 0 && !data1.IsLive) || (data1.Index == 1 && !data1.IsLive))
|
||||
})
|
||||
t.Run("previous epoch", func(t *testing.T) {
|
||||
resp, err := server.GetLiveness(ctx, ðpbv2.GetLivenessRequest{
|
||||
Epoch: 1,
|
||||
Index: []primitives.ValidatorIndex{0, 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
data0 := resp.Data[0]
|
||||
data1 := resp.Data[1]
|
||||
assert.Equal(t, true, (data0.Index == 0 && !data0.IsLive) || (data0.Index == 1 && data0.IsLive))
|
||||
assert.Equal(t, true, (data1.Index == 0 && !data1.IsLive) || (data1.Index == 1 && data1.IsLive))
|
||||
})
|
||||
t.Run("current epoch", func(t *testing.T) {
|
||||
resp, err := server.GetLiveness(ctx, ðpbv2.GetLivenessRequest{
|
||||
Epoch: 2,
|
||||
Index: []primitives.ValidatorIndex{0, 1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
data0 := resp.Data[0]
|
||||
data1 := resp.Data[1]
|
||||
assert.Equal(t, true, (data0.Index == 0 && data0.IsLive) || (data0.Index == 1 && !data0.IsLive))
|
||||
assert.Equal(t, true, (data1.Index == 0 && data1.IsLive) || (data1.Index == 1 && !data1.IsLive))
|
||||
})
|
||||
t.Run("future epoch", func(t *testing.T) {
|
||||
_, err := server.GetLiveness(ctx, ðpbv2.GetLivenessRequest{
|
||||
Epoch: 3,
|
||||
Index: []primitives.ValidatorIndex{0, 1},
|
||||
})
|
||||
require.ErrorContains(t, "Requested epoch cannot be in the future", err)
|
||||
})
|
||||
t.Run("unknown validator index", func(t *testing.T) {
|
||||
_, err := server.GetLiveness(ctx, ðpbv2.GetLivenessRequest{
|
||||
Epoch: 0,
|
||||
Index: []primitives.ValidatorIndex{0, 1, 2},
|
||||
})
|
||||
require.ErrorContains(t, "Validator index 2 is invalid", err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ go_library(
|
||||
"//api/pagination:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
blockfeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/block"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/operation"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
@@ -32,7 +32,7 @@ type Server struct {
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
CanonicalFetcher blockchain.CanonicalFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
DepositFetcher depositcache.DepositFetcher
|
||||
DepositFetcher cache.DepositFetcher
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
StateNotifier statefeed.Notifier
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
depositCache "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
type infostream struct {
|
||||
ctx context.Context
|
||||
headFetcher blockchain.HeadFetcher
|
||||
depositFetcher depositcache.DepositFetcher
|
||||
depositFetcher depositCache.DepositFetcher
|
||||
blockFetcher execution.POWBlockFetcher
|
||||
beaconDB db.ReadOnlyDatabase
|
||||
pubKeys [][]byte
|
||||
|
||||
@@ -184,11 +184,13 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.
|
||||
}
|
||||
// Cache proposer assignment for the current epoch.
|
||||
for _, slot := range proposerIndexToSlots[idx] {
|
||||
log.Infof("Setting payload id for slot %d , index %d in the current epoch", slot, idx)
|
||||
// Head root is empty because it can't be known until slot - 1. Same with payload id.
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, idx, [8]byte{} /* payloadID */, [32]byte{} /* head root */)
|
||||
}
|
||||
// Cache proposer assignment for the next epoch.
|
||||
for _, slot := range nextProposerIndexToSlots[idx] {
|
||||
log.Infof("Setting payload id for slot %d , index %d in the next epoch", slot, idx)
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, idx, [8]byte{} /* payloadID */, [32]byte{} /* head root */)
|
||||
}
|
||||
// Prune payload ID cache for any slots before request slot.
|
||||
|
||||
@@ -107,10 +107,10 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
}
|
||||
sBlk.SetProposerIndex(idx)
|
||||
|
||||
var blindBlobs []*ethpb.BlindedBlobSidecar
|
||||
var fullBlobs []*ethpb.BlobSidecar
|
||||
var blobBundle *enginev1.BlobsBundle
|
||||
var blindBlobBundle *enginev1.BlindedBlobsBundle
|
||||
if features.Get().BuildBlockParallel {
|
||||
blindBlobs, fullBlobs, err = vs.BuildBlockParallel(ctx, sBlk, head)
|
||||
blindBlobBundle, blobBundle, err = vs.BuildBlockParallel(ctx, sBlk, head)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not build block in parallel")
|
||||
}
|
||||
@@ -146,15 +146,16 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
vs.setSyncAggregate(ctx, sBlk)
|
||||
|
||||
// Get local and builder (if enabled) payloads. Set execution data. New in Bellatrix.
|
||||
localPayload, blobsBundle, overrideBuilder, err := vs.getLocalPayloadAndBlobs(ctx, sBlk.Block(), head)
|
||||
var overrideBuilder bool
|
||||
var localPayload interfaces.ExecutionData
|
||||
localPayload, blobBundle, overrideBuilder, err = vs.getLocalPayloadAndBlobs(ctx, sBlk.Block(), head)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get local payload: %v", err)
|
||||
}
|
||||
// There's no reason to try to get a builder bid if local override is true.
|
||||
var builderPayload interfaces.ExecutionData
|
||||
var blindBlobsBundle *enginev1.BlindedBlobsBundle
|
||||
if !overrideBuilder {
|
||||
builderPayload, blindBlobsBundle, err = vs.getBuilderPayloadAndBlobs(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex())
|
||||
builderPayload, blindBlobBundle, err = vs.getBuilderPayloadAndBlobs(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex())
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Error("Could not get builder payload")
|
||||
@@ -167,19 +168,9 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
// Set bls to execution change. New in Capella.
|
||||
vs.setBlsToExecData(sBlk, head)
|
||||
|
||||
if err := setKzgCommitments(sBlk, blobsBundle, blindBlobsBundle); err != nil {
|
||||
if err := setKzgCommitments(sBlk, blobBundle, blindBlobBundle); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set kzg commitment: %v", err)
|
||||
}
|
||||
|
||||
// Covert blobs bundle to sidecars.
|
||||
fullBlobs, err = blobsBundleToSidecars(blobsBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
blindBlobs, err = blindBlobsBundleToSidecars(blindBlobsBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert blind blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sr, err := vs.computeStateRoot(ctx, sBlk)
|
||||
@@ -188,6 +179,15 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
}
|
||||
sBlk.SetStateRoot(sr)
|
||||
|
||||
fullBlobs, err := blobsBundleToSidecars(blobBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
blindBlobs, err := blindBlobsBundleToSidecars(blindBlobBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert blind blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": req.Slot,
|
||||
"sinceSlotStartTime": time.Since(t),
|
||||
@@ -204,14 +204,14 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
Block: pb.(*ethpb.BlindedBeaconBlockDeneb),
|
||||
Blobs: blindBlobs,
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedDeneb{BlindedDeneb: blockAndBlobs}, IsBlinded: true, PayloadValue: sBlk.ValueInGwei()}, nil
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_BlindedDeneb{BlindedDeneb: blockAndBlobs}}, nil
|
||||
}
|
||||
|
||||
blockAndBlobs := ðpb.BeaconBlockAndBlobsDeneb{
|
||||
Block: pb.(*ethpb.BeaconBlockDeneb),
|
||||
Blobs: fullBlobs,
|
||||
}
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Deneb{Deneb: blockAndBlobs}, IsBlinded: false, PayloadValue: sBlk.ValueInGwei()}, nil
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Deneb{Deneb: blockAndBlobs}}, nil
|
||||
}
|
||||
|
||||
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().CapellaForkEpoch {
|
||||
@@ -232,7 +232,7 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Phase0{Phase0: pb.(*ethpb.BeaconBlock)}, IsBlinded: false, PayloadValue: 0}, nil
|
||||
}
|
||||
|
||||
func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState) ([]*ethpb.BlindedBlobSidecar, []*ethpb.BlobSidecar, error) {
|
||||
func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState) (*enginev1.BlindedBlobsBundle, *enginev1.BlobsBundle, error) {
|
||||
// Build consensus fields in background
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -281,7 +281,6 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
// There's no reason to try to get a builder bid if local override is true.
|
||||
var builderPayload interfaces.ExecutionData
|
||||
var blindBlobsBundle *enginev1.BlindedBlobsBundle
|
||||
var blindBlobs []*ethpb.BlindedBlobSidecar
|
||||
if !overrideBuilder {
|
||||
builderPayload, blindBlobsBundle, err = vs.getBuilderPayloadAndBlobs(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex())
|
||||
if err != nil {
|
||||
@@ -298,18 +297,9 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
return nil, nil, status.Errorf(codes.Internal, "Could not set kzg commitment: %v", err)
|
||||
}
|
||||
|
||||
fullBlobs, err := blobsBundleToSidecars(blobsBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.Internal, "Could not convert blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
blindBlobs, err = blindBlobsBundleToSidecars(blindBlobsBundle, sBlk.Block())
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.Internal, "Could not convert blind blobs bundle to sidecar: %v", err)
|
||||
}
|
||||
|
||||
wg.Wait() // Wait until block is built via consensus and execution fields.
|
||||
|
||||
return blindBlobs, fullBlobs, nil
|
||||
return blindBlobsBundle, blobsBundle, nil
|
||||
}
|
||||
|
||||
// ProposeBeaconBlock is called by a proposer during its assigned slot to create a block in an attempt
|
||||
@@ -325,7 +315,7 @@ func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSign
|
||||
|
||||
var blindSidecars []*ethpb.SignedBlindedBlobSidecar
|
||||
if blk.Version() >= version.Deneb && blk.IsBlinded() {
|
||||
blindSidecars = req.GetBlindedDeneb().Blobs
|
||||
blindSidecars = req.GetBlindedDeneb().SignedBlindedBlobSidecars
|
||||
}
|
||||
|
||||
unblinder, err := newUnblinder(blk, blindSidecars, vs.BlockBuilder)
|
||||
@@ -360,6 +350,10 @@ func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSign
|
||||
}
|
||||
sidecars := make([]*ethpb.BlobSidecar, len(scs))
|
||||
for i, sc := range scs {
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockRoot": hex.EncodeToString(sc.Message.BlockRoot),
|
||||
"index": sc.Message.Index,
|
||||
}).Debug("Broadcasting blob sidecar")
|
||||
if err := vs.P2P.BroadcastBlob(ctx, sc.Message.Index, sc); err != nil {
|
||||
log.WithError(err).Errorf("Could not broadcast blob sidecar index %d / %d", i, len(scs))
|
||||
}
|
||||
|
||||
@@ -215,7 +215,9 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot primitiv
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not get blinded blobs bundle")
|
||||
}
|
||||
log.WithField("blindBlobCount", len(bundle.BlobRoots))
|
||||
if bundle != nil {
|
||||
log.WithField("blindBlobCount", len(bundle.BlobRoots))
|
||||
}
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
|
||||
@@ -22,9 +22,15 @@ func setKzgCommitments(blk interfaces.SignedBeaconBlock, bundle *enginev1.BlobsB
|
||||
}
|
||||
|
||||
if blk.IsBlinded() {
|
||||
if blindBundle == nil {
|
||||
return nil
|
||||
}
|
||||
return blk.SetBlobKzgCommitments(blindBundle.KzgCommitments)
|
||||
}
|
||||
|
||||
if bundle == nil {
|
||||
return nil
|
||||
}
|
||||
return blk.SetBlobKzgCommitments(bundle.KzgCommitments)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
@@ -129,16 +130,19 @@ func (vs *Server) deposits(
|
||||
return pendingDeposits, nil
|
||||
}
|
||||
|
||||
func (vs *Server) depositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (*trie.SparseMerkleTrie, error) {
|
||||
func (vs *Server) depositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (cache.MerkleTree, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.depositTrie")
|
||||
defer span.End()
|
||||
|
||||
var depositTrie *trie.SparseMerkleTrie
|
||||
var depositTrie cache.MerkleTree
|
||||
|
||||
finalizedDeposits := vs.DepositFetcher.FinalizedDeposits(ctx)
|
||||
depositTrie = finalizedDeposits.Deposits
|
||||
upToEth1DataDeposits := vs.DepositFetcher.NonFinalizedDeposits(ctx, finalizedDeposits.MerkleTrieIndex, canonicalEth1DataHeight)
|
||||
insertIndex := finalizedDeposits.MerkleTrieIndex + 1
|
||||
finalizedDeposits, err := vs.DepositFetcher.FinalizedDeposits(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depositTrie = finalizedDeposits.Deposits()
|
||||
upToEth1DataDeposits := vs.DepositFetcher.NonFinalizedDeposits(ctx, finalizedDeposits.MerkleTrieIndex(), canonicalEth1DataHeight)
|
||||
insertIndex := finalizedDeposits.MerkleTrieIndex() + 1
|
||||
|
||||
if shouldRebuildTrie(canonicalEth1Data.DepositCount, uint64(len(upToEth1DataDeposits))) {
|
||||
log.WithFields(logrus.Fields{
|
||||
@@ -169,7 +173,7 @@ func (vs *Server) depositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1
|
||||
|
||||
// rebuilds our deposit trie by recreating it from all processed deposits till
|
||||
// specified eth1 block height.
|
||||
func (vs *Server) rebuildDepositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (*trie.SparseMerkleTrie, error) {
|
||||
func (vs *Server) rebuildDepositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (cache.MerkleTree, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.rebuildDepositTrie")
|
||||
defer span.End()
|
||||
|
||||
@@ -196,7 +200,7 @@ func (vs *Server) rebuildDepositTrie(ctx context.Context, canonicalEth1Data *eth
|
||||
}
|
||||
|
||||
// validate that the provided deposit trie matches up with the canonical eth1 data provided.
|
||||
func validateDepositTrie(trie *trie.SparseMerkleTrie, canonicalEth1Data *ethpb.Eth1Data) (bool, error) {
|
||||
func validateDepositTrie(trie cache.MerkleTree, canonicalEth1Data *ethpb.Eth1Data) (bool, error) {
|
||||
if trie == nil || canonicalEth1Data == nil {
|
||||
return false, errors.New("nil trie or eth1data provided")
|
||||
}
|
||||
@@ -213,7 +217,7 @@ func validateDepositTrie(trie *trie.SparseMerkleTrie, canonicalEth1Data *ethpb.E
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func constructMerkleProof(trie *trie.SparseMerkleTrie, index int, deposit *ethpb.Deposit) (*ethpb.Deposit, error) {
|
||||
func constructMerkleProof(trie cache.MerkleTree, index int, deposit *ethpb.Deposit) (*ethpb.Deposit, error) {
|
||||
proof, err := trie.MerkleProof(index)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not generate merkle proof for deposit at index %d", index)
|
||||
|
||||
@@ -548,6 +548,11 @@ func TestServer_GetBeaconBlock_Deneb(t *testing.T) {
|
||||
got, err := proposerServer.GetBeaconBlock(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, got.GetDeneb().Block.Body.BlobKzgCommitments, kc)
|
||||
|
||||
require.Equal(t, 3, len(got.GetDeneb().Blobs))
|
||||
blockRoot, err := got.GetDeneb().Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, blockRoot[:], got.GetDeneb().Blobs[0].BlockRoot)
|
||||
}
|
||||
|
||||
func TestServer_GetBeaconBlock_Optimistic(t *testing.T) {
|
||||
@@ -745,17 +750,17 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
|
||||
name: "blind capella",
|
||||
block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock {
|
||||
blockToPropose := util.NewBlindedBeaconBlockDeneb()
|
||||
blockToPropose.Block.Slot = 5
|
||||
blockToPropose.Block.ParentRoot = parent[:]
|
||||
blockToPropose.Message.Slot = 5
|
||||
blockToPropose.Message.ParentRoot = parent[:]
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot, err := ssz.WithdrawalSliceRoot([]*enginev1.Withdrawal{}, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
blockToPropose.Block.Body.ExecutionPayloadHeader.TransactionsRoot = txRoot[:]
|
||||
blockToPropose.Block.Body.ExecutionPayloadHeader.WithdrawalsRoot = withdrawalsRoot[:]
|
||||
blockToPropose.Message.Body.ExecutionPayloadHeader.TransactionsRoot = txRoot[:]
|
||||
blockToPropose.Message.Body.ExecutionPayloadHeader.WithdrawalsRoot = withdrawalsRoot[:]
|
||||
blk := ðpb.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: ðpb.SignedBlindedBeaconBlockAndBlobsDeneb{
|
||||
Block: blockToPropose,
|
||||
Blobs: []*ethpb.SignedBlindedBlobSidecar{
|
||||
SignedBlindedBlock: blockToPropose,
|
||||
SignedBlindedBlobSidecars: []*ethpb.SignedBlindedBlobSidecar{
|
||||
{
|
||||
Message: ðpb.BlindedBlobSidecar{
|
||||
BlockRoot: []byte{0x01},
|
||||
@@ -1694,7 +1699,7 @@ func TestProposer_DepositTrie_RebuildTrie(t *testing.T) {
|
||||
// Mutate it since its a pointer
|
||||
d[0].Deposit.Data.WithdrawalCredentials = junkCreds[:]
|
||||
// Insert junk to corrupt trie.
|
||||
err = depositCache.InsertFinalizedDeposits(ctx, 2)
|
||||
err = depositCache.InsertFinalizedDeposits(ctx, 2, [32]byte{}, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add original back
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/network/forks"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -50,7 +51,7 @@ type Server struct {
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
TimeFetcher blockchain.TimeFetcher
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
DepositFetcher depositcache.DepositFetcher
|
||||
DepositFetcher cache.DepositFetcher
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
Eth1InfoFetcher execution.ChainInfoFetcher
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
@@ -138,12 +139,26 @@ func (vs *Server) ValidatorIndex(ctx context.Context, req *ethpb.ValidatorIndexR
|
||||
}
|
||||
|
||||
// DomainData fetches the current domain version information from the beacon state.
|
||||
func (vs *Server) DomainData(_ context.Context, request *ethpb.DomainRequest) (*ethpb.DomainResponse, error) {
|
||||
func (vs *Server) DomainData(ctx context.Context, request *ethpb.DomainRequest) (*ethpb.DomainResponse, error) {
|
||||
fork, err := forks.Fork(request.Epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headGenesisValidatorsRoot := vs.HeadFetcher.HeadGenesisValidatorsRoot()
|
||||
isExitDomain := [4]byte(request.Domain) == params.BeaconConfig().DomainVoluntaryExit
|
||||
if isExitDomain {
|
||||
hs, err := vs.HeadFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hs.Version() >= version.Deneb {
|
||||
fork = ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
}
|
||||
}
|
||||
}
|
||||
dv, err := signing.Domain(fork, request.Epoch, bytesutil.ToBytes4(request.Domain), headGenesisValidatorsRoot[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
mockChain "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
mockExecution "github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/startup"
|
||||
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
@@ -309,3 +311,56 @@ func TestWaitForChainStart_NotStartedThenLogFired(t *testing.T) {
|
||||
exitRoutine <- true
|
||||
require.LogsContain(t, hook, "Sending genesis time")
|
||||
}
|
||||
|
||||
func TestServer_DomainData_Exits(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ForkVersionSchedule = map[[4]byte]primitives.Epoch{
|
||||
[4]byte(cfg.GenesisForkVersion): primitives.Epoch(0),
|
||||
[4]byte(cfg.AltairForkVersion): primitives.Epoch(5),
|
||||
[4]byte(cfg.BellatrixForkVersion): primitives.Epoch(10),
|
||||
[4]byte(cfg.CapellaForkVersion): primitives.Epoch(15),
|
||||
[4]byte(cfg.DenebForkVersion): primitives.Epoch(20),
|
||||
}
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
beaconState := ðpb.BeaconStateBellatrix{
|
||||
Slot: 4000,
|
||||
}
|
||||
block := util.NewBeaconBlock()
|
||||
genesisRoot, err := block.Block.HashTreeRoot()
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
s, err := state_native.InitializeFromProtoUnsafeBellatrix(beaconState)
|
||||
require.NoError(t, err)
|
||||
vs := &Server{
|
||||
Ctx: context.Background(),
|
||||
ChainStartFetcher: &mockExecution.Chain{},
|
||||
HeadFetcher: &mockChain.ChainService{State: s, Root: genesisRoot[:]},
|
||||
}
|
||||
|
||||
reqDomain, err := vs.DomainData(context.Background(), ðpb.DomainRequest{
|
||||
Epoch: 100,
|
||||
Domain: params.BeaconConfig().DomainDeposit[:],
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
wantedDomain, err := signing.ComputeDomain(params.BeaconConfig().DomainDeposit, params.BeaconConfig().DenebForkVersion, make([]byte, 32))
|
||||
assert.NoError(t, err)
|
||||
assert.DeepEqual(t, reqDomain.SignatureDomain, wantedDomain)
|
||||
|
||||
beaconStateNew := ðpb.BeaconStateDeneb{
|
||||
Slot: 4000,
|
||||
}
|
||||
s, err = state_native.InitializeFromProtoUnsafeDeneb(beaconStateNew)
|
||||
require.NoError(t, err)
|
||||
vs.HeadFetcher = &mockChain.ChainService{State: s, Root: genesisRoot[:]}
|
||||
|
||||
reqDomain, err = vs.DomainData(context.Background(), ðpb.DomainRequest{
|
||||
Epoch: 100,
|
||||
Domain: params.BeaconConfig().DomainVoluntaryExit[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
wantedDomain, err = signing.ComputeDomain(params.BeaconConfig().DomainVoluntaryExit, params.BeaconConfig().CapellaForkVersion, make([]byte, 32))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.DeepEqual(t, reqDomain.SignatureDomain, wantedDomain)
|
||||
}
|
||||
|
||||
@@ -158,6 +158,10 @@ func copyBlockData(src interfaces.SignedBeaconBlock, dst interfaces.SignedBeacon
|
||||
if err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not get bls to execution changes")
|
||||
}
|
||||
kzgCommitments, err := src.Block().Body().BlobKzgCommitments()
|
||||
if err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not get blob kzg commitments")
|
||||
}
|
||||
|
||||
dst.SetSlot(src.Block().Slot())
|
||||
dst.SetProposerIndex(src.Block().ProposerIndex())
|
||||
@@ -178,6 +182,9 @@ func copyBlockData(src interfaces.SignedBeaconBlock, dst interfaces.SignedBeacon
|
||||
if err = dst.SetBLSToExecutionChanges(blsToExecChanges); err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not set bls to execution changes")
|
||||
}
|
||||
if err = dst.SetBlobKzgCommitments(kzgCommitments); err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not set bls to execution changes")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -198,7 +205,7 @@ func (u *unblinder) blindedProtoBlock() (proto.Message, error) {
|
||||
}, nil
|
||||
case version.Deneb:
|
||||
return ðpb.SignedBlindedBeaconBlockDeneb{
|
||||
Block: ðpb.BlindedBeaconBlockDeneb{
|
||||
Message: ðpb.BlindedBeaconBlockDeneb{
|
||||
Body: ðpb.BlindedBeaconBlockBodyDeneb{},
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -276,9 +276,9 @@ func Test_unblindBuilderBlock(t *testing.T) {
|
||||
}(),
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockDeneb()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
b.Message.Slot = 1
|
||||
b.Message.ProposerIndex = 2
|
||||
b.Message.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
{
|
||||
Message: ð.BLSToExecutionChange{
|
||||
ValidatorIndex: 123,
|
||||
@@ -296,11 +296,12 @@ func Test_unblindBuilderBlock(t *testing.T) {
|
||||
Signature: []byte("sig456"),
|
||||
},
|
||||
}
|
||||
b.Message.Body.BlobKzgCommitments = [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}, {'c', 5}}
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot, err := ssz.WithdrawalSliceRoot([]*v1.Withdrawal{}, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeaderDeneb{
|
||||
b.Message.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
@@ -350,6 +351,7 @@ func Test_unblindBuilderBlock(t *testing.T) {
|
||||
Signature: []byte("sig456"),
|
||||
},
|
||||
}
|
||||
b.Block.Body.BlobKzgCommitments = [][]byte{{'c', 0}, {'c', 1}, {'c', 2}, {'c', 3}, {'c', 4}, {'c', 5}}
|
||||
b.Block.Body.ExecutionPayload = pDeneb
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user