mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-03 08:37:37 +00:00
5a66807989
* First take at updating everything to v5 * Patch gRPC gateway to use prysm v5 Fix patch * Update go ssz --------- Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
373 lines
10 KiB
Go
373 lines
10 KiB
Go
package depositsnapshot
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/bazelbuild/rules_go/go/tools/bazel"
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/v5/container/trie"
|
|
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
|
|
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/v5/io/file"
|
|
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type testCase struct {
|
|
DepositData depositData `yaml:"deposit_data"`
|
|
DepositDataRoot [32]byte `yaml:"deposit_data_root"`
|
|
Eth1Data *eth1Data `yaml:"eth1_data"`
|
|
BlockHeight uint64 `yaml:"block_height"`
|
|
Snapshot snapshot `yaml:"snapshot"`
|
|
}
|
|
|
|
func (tc *testCase) UnmarshalYAML(value *yaml.Node) error {
|
|
raw := struct {
|
|
DepositData depositData `yaml:"deposit_data"`
|
|
DepositDataRoot string `yaml:"deposit_data_root"`
|
|
Eth1Data *eth1Data `yaml:"eth1_data"`
|
|
BlockHeight string `yaml:"block_height"`
|
|
Snapshot snapshot `yaml:"snapshot"`
|
|
}{}
|
|
err := value.Decode(&raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tc.DepositDataRoot, err = hexStringToByteArray(raw.DepositDataRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tc.DepositData = raw.DepositData
|
|
tc.Eth1Data = raw.Eth1Data
|
|
tc.BlockHeight, err = stringToUint64(raw.BlockHeight)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tc.Snapshot = raw.Snapshot
|
|
return nil
|
|
}
|
|
|
|
type depositData struct {
|
|
Pubkey []byte `yaml:"pubkey"`
|
|
WithdrawalCredentials []byte `yaml:"withdrawal_credentials"`
|
|
Amount uint64 `yaml:"amount"`
|
|
Signature []byte `yaml:"signature"`
|
|
}
|
|
|
|
func (dd *depositData) UnmarshalYAML(value *yaml.Node) error {
|
|
raw := struct {
|
|
Pubkey string `yaml:"pubkey"`
|
|
WithdrawalCredentials string `yaml:"withdrawal_credentials"`
|
|
Amount string `yaml:"amount"`
|
|
Signature string `yaml:"signature"`
|
|
}{}
|
|
err := value.Decode(&raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dd.Pubkey, err = hexStringToBytes(raw.Pubkey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dd.WithdrawalCredentials, err = hexStringToBytes(raw.WithdrawalCredentials)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dd.Amount, err = strconv.ParseUint(raw.Amount, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dd.Signature, err = hexStringToBytes(raw.Signature)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type eth1Data struct {
|
|
DepositRoot [32]byte `yaml:"deposit_root"`
|
|
DepositCount uint64 `yaml:"deposit_count"`
|
|
BlockHash [32]byte `yaml:"block_hash"`
|
|
}
|
|
|
|
func (ed *eth1Data) UnmarshalYAML(value *yaml.Node) error {
|
|
raw := struct {
|
|
DepositRoot string `yaml:"deposit_root"`
|
|
DepositCount string `yaml:"deposit_count"`
|
|
BlockHash string `yaml:"block_hash"`
|
|
}{}
|
|
err := value.Decode(&raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ed.DepositRoot, err = hexStringToByteArray(raw.DepositRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ed.DepositCount, err = stringToUint64(raw.DepositCount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ed.BlockHash, err = hexStringToByteArray(raw.BlockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type snapshot struct {
|
|
DepositTreeSnapshot
|
|
}
|
|
|
|
func (sd *snapshot) UnmarshalYAML(value *yaml.Node) error {
|
|
raw := struct {
|
|
Finalized []string `yaml:"finalized"`
|
|
DepositRoot string `yaml:"deposit_root"`
|
|
DepositCount string `yaml:"deposit_count"`
|
|
ExecutionBlockHash string `yaml:"execution_block_hash"`
|
|
ExecutionBlockHeight string `yaml:"execution_block_height"`
|
|
}{}
|
|
err := value.Decode(&raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sd.finalized = make([][32]byte, len(raw.Finalized))
|
|
for i, finalized := range raw.Finalized {
|
|
sd.finalized[i], err = hexStringToByteArray(finalized)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
sd.depositRoot, err = hexStringToByteArray(raw.DepositRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sd.depositCount, err = stringToUint64(raw.DepositCount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sd.executionBlock.Hash, err = hexStringToByteArray(raw.ExecutionBlockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sd.executionBlock.Depth, err = stringToUint64(raw.ExecutionBlockHeight)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readTestCases() ([]testCase, error) {
|
|
testFolders, err := bazel.ListRunfiles()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ff := range testFolders {
|
|
if strings.Contains(ff.ShortPath, "eip4881_spec_tests") &&
|
|
strings.Contains(ff.ShortPath, "eip-4881/test_cases.yaml") {
|
|
enc, err := file.ReadFileAsBytes(ff.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var testCases []testCase
|
|
err = yaml.Unmarshal(enc, &testCases)
|
|
if err != nil {
|
|
return []testCase{}, err
|
|
}
|
|
if len(testCases) == 0 {
|
|
return nil, errors.New("no test cases found")
|
|
}
|
|
return testCases, nil
|
|
}
|
|
}
|
|
return nil, errors.New("spec test file not found")
|
|
}
|
|
|
|
func TestRead(t *testing.T) {
|
|
_, err := readTestCases()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func hexStringToByteArray(s string) (b [32]byte, err error) {
|
|
var raw []byte
|
|
raw, err = hexStringToBytes(s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(raw) != 32 {
|
|
err = errors.New("invalid hex string length")
|
|
return
|
|
}
|
|
copy(b[:], raw[:32])
|
|
return
|
|
}
|
|
|
|
func hexStringToBytes(s string) (b []byte, err error) {
|
|
b, err = hex.DecodeString(strings.TrimPrefix(s, "0x"))
|
|
return
|
|
}
|
|
|
|
func stringToUint64(s string) (uint64, error) {
|
|
value, err := strconv.ParseUint(s, 10, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func merkleRootFromBranch(leaf [32]byte, branch [][32]byte, index uint64) [32]byte {
|
|
root := leaf
|
|
for i, l := range branch {
|
|
ithBit := (index >> i) & 0x1
|
|
if ithBit == 1 {
|
|
root = hash.Hash(append(l[:], root[:]...))
|
|
} else {
|
|
root = hash.Hash(append(root[:], l[:]...))
|
|
}
|
|
}
|
|
return root
|
|
}
|
|
|
|
func checkProof(t *testing.T, tree *DepositTree, index uint64) {
|
|
leaf, proof, err := tree.getProof(index)
|
|
require.NoError(t, err)
|
|
calcRoot := merkleRootFromBranch(leaf, proof, index)
|
|
require.Equal(t, tree.getRoot(), calcRoot)
|
|
}
|
|
|
|
func compareProof(t *testing.T, tree1, tree2 *DepositTree, index uint64) {
|
|
require.Equal(t, tree1.getRoot(), tree2.getRoot())
|
|
checkProof(t, tree1, index)
|
|
checkProof(t, tree2, index)
|
|
}
|
|
|
|
func cloneFromSnapshot(t *testing.T, snapshot DepositTreeSnapshot, testCases []testCase) *DepositTree {
|
|
cp, err := fromSnapshot(snapshot)
|
|
require.NoError(t, err)
|
|
for _, c := range testCases {
|
|
err = cp.pushLeaf(c.DepositDataRoot)
|
|
require.NoError(t, err)
|
|
}
|
|
return cp
|
|
}
|
|
|
|
func TestDepositCases(t *testing.T) {
|
|
tree := NewDepositTree()
|
|
testCases, err := readTestCases()
|
|
require.NoError(t, err)
|
|
for _, c := range testCases {
|
|
err = tree.pushLeaf(c.DepositDataRoot)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
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 := NewDepositTree()
|
|
testCases, err := readTestCases()
|
|
require.NoError(t, err)
|
|
for _, c := range testCases[:128] {
|
|
err = tree.pushLeaf(c.DepositDataRoot)
|
|
require.NoError(t, err)
|
|
}
|
|
originalRoot := tree.getRoot()
|
|
require.DeepEqual(t, testCases[127].Eth1Data.DepositRoot, originalRoot)
|
|
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()
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, testCases[100].Snapshot.DepositTreeSnapshot, snapshotData)
|
|
// create a copy of the tree from a snapshot by replaying
|
|
// the deposits after the finalized deposit
|
|
cp := cloneFromSnapshot(t, snapshotData, testCases[101:128])
|
|
// 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(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()
|
|
require.NoError(t, err)
|
|
cp = cloneFromSnapshot(t, snapshotData, testCases[106:128])
|
|
// create a copy of the tree by replaying ALL deposits from nothing
|
|
fullTreeCopy := NewDepositTree()
|
|
for _, c := range testCases[:128] {
|
|
err = fullTreeCopy.pushLeaf(c.DepositDataRoot)
|
|
require.NoError(t, err)
|
|
}
|
|
for i := 106; i < 128; i++ {
|
|
compareProof(t, tree, cp, uint64(i))
|
|
compareProof(t, tree, fullTreeCopy, uint64(i))
|
|
}
|
|
}
|
|
|
|
func TestSnapshotCases(t *testing.T) {
|
|
tree := NewDepositTree()
|
|
testCases, err := readTestCases()
|
|
require.NoError(t, err)
|
|
for _, c := range testCases {
|
|
err = tree.pushLeaf(c.DepositDataRoot)
|
|
require.NoError(t, err)
|
|
}
|
|
for _, c := range testCases {
|
|
err = tree.Finalize(int64(c.Eth1Data.DepositCount-1), c.Eth1Data.BlockHash, c.BlockHeight)
|
|
require.NoError(t, err)
|
|
s, err := tree.GetSnapshot()
|
|
require.NoError(t, err)
|
|
require.DeepEqual(t, c.Snapshot.DepositTreeSnapshot, s)
|
|
}
|
|
}
|
|
|
|
func TestInvalidSnapshot(t *testing.T) {
|
|
invalidSnapshot := DepositTreeSnapshot{
|
|
finalized: nil,
|
|
depositRoot: trie.ZeroHashes[0],
|
|
depositCount: 0,
|
|
executionBlock: executionBlock{
|
|
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")
|
|
}
|