prysm-pulse/beacon-chain/cache/depositsnapshot/spec_test.go
terence 5a66807989
Update to V5 (#13622)
* 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>
2024-02-15 05:46:47 +00:00

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")
}