Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recent history integration test #138

Merged
merged 1 commit into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions internal/merkle/mountain_ranges/mmr.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ func New() *MMR {

// Append (A function): A(r, l, H) ↦ P(r, l, 0, H) from equation (327)
func (m *MMR) Append(r []*crypto.Hash, l crypto.Hash, hashFunc func([]byte) crypto.Hash) []*crypto.Hash {
hash := hashFunc(l[:])
return placePeak(r, &hash, 0, hashFunc)
return placePeak(r, &l, 0, hashFunc)
}

// placePeak implements P function from equation (327):
Expand Down
58 changes: 45 additions & 13 deletions internal/statetransition/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,52 @@ func calculateNewTimeState(header block.Header) jamtime.Timeslot {

// calculateNewRecentBlocks Equation 18: β′ ≺ (H, EG, β†, C) v0.4.5
func calculateNewRecentBlocks(header block.Header, guarantees block.GuaranteesExtrinsic, intermediateRecentBlocks []state.BlockState, serviceHashPairs ServiceHashPairs) ([]state.BlockState, error) {
// Gather all the inputs we need.

// Equation 83: let n = {p, h ▸▸ H(H), b, s ▸▸ H_0}
headerBytes, err := jam.Marshal(header)
if err != nil {
return nil, err
}
headerHash := crypto.HashData(headerBytes)

priorStateRoot := header.PriorStateRoot

// Equation 83: let r = M_B([s ^^ E_4(s) ⌢ E(h) | (s, h) ∈ C], H_K)
accumulationRoot, err := computeAccumulationRoot(serviceHashPairs)
if err != nil {
return nil, err
}

// Equation 83: p = {((g_w)_s)_h ↦ ((g_w)_s)_e | g ∈ E_G}
workPackageMapping := buildWorkPackageMapping(guarantees.Guarantees)

// Update β to produce β'.
newRecentBlocks, err := UpdateRecentBlocks(headerHash, priorStateRoot, accumulationRoot, intermediateRecentBlocks, workPackageMapping)
if err != nil {
return nil, err
}

return newRecentBlocks, nil
}

// UpdateRecentBlocks updates β. It takes the final inputs from
// Equation 83: let n = {p, h ▸▸ H(H), b, s ▸▸ H_0} and
// produces Equation 84: β′ ≡ ←────── β† n_H.
// We separate out this logic for ease of testing aganist the recent history
// test vectors.
func UpdateRecentBlocks(
headerHash crypto.Hash,
priorStateRoot crypto.Hash,
accumulationRoot crypto.Hash,
intermediateRecentBlocks []state.BlockState,
workPackageMapping map[crypto.Hash]crypto.Hash) (newRecentBlocks []state.BlockState, err error) {

// Equation 82: β†[SβS − 1]s = Hr
if len(intermediateRecentBlocks) > 0 {
intermediateRecentBlocks[len(intermediateRecentBlocks)-1].StateRoot = priorStateRoot
}

// Equation 83: let b = A(last([[]] ⌢ [x_b | x <− β]), r, H_K)
var lastBlockMMR []*crypto.Hash
if len(intermediateRecentBlocks) > 0 {
Expand All @@ -303,24 +343,16 @@ func calculateNewRecentBlocks(header block.Header, guarantees block.GuaranteesEx
// A(last([[]] ⌢ [x_b | x <− β]), r, H_K)
newMMR := mountainRange.Append(lastBlockMMR, accumulationRoot, crypto.KeccakData)

// Equation 83: p = {((g_w)_s)_h ↦ ((g_w)_s)_e | g ∈ E_G}
workPackageMapping := buildWorkPackageMapping(guarantees.Guarantees)

// Equation 83: let n = {p, h ▸▸ H(H), b, s ▸▸ H_0}
headerBytes, err := jam.Marshal(header)
if err != nil {
return nil, err
}
newBlockState := state.BlockState{
HeaderHash: crypto.HashData(headerBytes), // h ▸▸ H(H)
StateRoot: crypto.Hash{}, // s ▸▸ H_0
AccumulationResultMMR: newMMR, // b
WorkReportHashes: workPackageMapping, // p
HeaderHash: headerHash, // h ▸▸ H(H)
StateRoot: crypto.Hash{}, // s ▸▸ H_0
AccumulationResultMMR: newMMR, // b
WorkReportHashes: workPackageMapping, // p
}

// Equation 84: β′ ≡ ←────── β† n_H
// First append new block state
newRecentBlocks := append(intermediateRecentBlocks, newBlockState)
newRecentBlocks = append(intermediateRecentBlocks, newBlockState)

// Then keep only last H blocks
if len(newRecentBlocks) > state.MaxRecentBlocks {
Expand Down
124 changes: 124 additions & 0 deletions tests/integration/recent_history_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//go:build integration

package integration_test

import (
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/eigerco/strawberry/internal/crypto"
"github.com/eigerco/strawberry/internal/state"
"github.com/eigerco/strawberry/internal/statetransition"
"github.com/eigerco/strawberry/internal/testutils"
"github.com/stretchr/testify/require"
)

func TestRecentHistory(t *testing.T) {
testFiles := []string{
// Empty hjstory queue.
"vectors/recenthistory/progress_blocks_history-1.json",

// Not empty nor full history queue.
"vectors/recenthistory/progress_blocks_history-2.json",

// Fill the history queue.
"vectors/recenthistory/progress_blocks_history-3.json",

// Shift the history queue.
"vectors/recenthistory/progress_blocks_history-4.json",
}

for _, tf := range testFiles {
t.Run(filepath.Base(tf), func(t *testing.T) {
file, err := os.ReadFile(tf)
require.NoError(t, err)

var tv RecentHistoryTestVector
err = json.Unmarshal(file, &tv)
require.NoError(t, err)

// Inputs.
headerHash := crypto.Hash(testutils.MustFromHex(t, tv.Input.HeaderHash))
parentStateRoot := crypto.Hash(testutils.MustFromHex(t, tv.Input.ParentStateRoot))
accumulateRoot := crypto.Hash(testutils.MustFromHex(t, tv.Input.AccumulateRoot))

preRecentBlocks := toRecentBlocks(t, tv.PreState)

workPackages := map[crypto.Hash]crypto.Hash{}
for _, wp := range tv.Input.WorkPackages {
hash := crypto.Hash(testutils.MustFromHex(t, wp.Hash))
exportsRoot := crypto.Hash(testutils.MustFromHex(t, wp.ExportsRoot))

workPackages[hash] = exportsRoot
}

newRecentBlocks, err := statetransition.UpdateRecentBlocks(headerHash, parentStateRoot, accumulateRoot, preRecentBlocks, workPackages)
require.NoError(t, err)

postRecentBlocks := toRecentBlocks(t, tv.PostState)
require.Equal(t, postRecentBlocks, newRecentBlocks)
})

}
}

func toRecentBlocks(t *testing.T, s RecentHistoryTestVectorState) []state.BlockState {
intermediateBlocks := make([]state.BlockState, len(s.Beta))
for i, bs := range s.Beta {
accResultMMR := make([]*crypto.Hash, len(bs.MMR.Peaks))
for i, p := range bs.MMR.Peaks {
if p != nil {
peak := crypto.Hash(testutils.MustFromHex(t, *p))
accResultMMR[i] = &peak
}
}

workReportHashes := map[crypto.Hash]crypto.Hash{}
for _, wr := range bs.Reported {
hash := crypto.Hash(testutils.MustFromHex(t, wr.Hash))
exportsRoot := crypto.Hash(testutils.MustFromHex(t, wr.ExportsRoot))

workReportHashes[hash] = exportsRoot
}

intermediateBlocks[i] = state.BlockState{
HeaderHash: crypto.Hash(testutils.MustFromHex(t, bs.HeaderHash)),
StateRoot: crypto.Hash(testutils.MustFromHex(t, bs.StateRoot)),
AccumulationResultMMR: accResultMMR,
WorkReportHashes: workReportHashes,
}
}

return intermediateBlocks
}

type RecentHistoryTestVector struct {
Input struct {
HeaderHash string `json:"header_hash"`
ParentStateRoot string `json:"parent_state_root"`
AccumulateRoot string `json:"accumulate_root"`
WorkPackages []struct {
Hash string `json:"hash"`
ExportsRoot string `json:"exports_root"`
} `json:"work_packages"`
} `json:"input"`
PreState RecentHistoryTestVectorState `json:"pre_state"`
Output interface{} `json:"output"`
PostState RecentHistoryTestVectorState `json:"post_state"`
}

type RecentHistoryTestVectorState struct {
Beta []struct {
HeaderHash string `json:"header_hash"`
MMR struct {
Peaks []*string `json:"peaks"`
} `json:"mmr"`
StateRoot string `json:"state_root"`
Reported []struct {
Hash string `json:"hash"`
ExportsRoot string `json:"exports_root"`
} `json:"reported"`
} `json:"beta"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"input": {
"header_hash": "0x530ef4636fedd498e99c7601581271894a53e965e901e8fa49581e525f165dae",
"parent_state_root": "0x0e6c6cbf80b5fb00175001f7b0966bf1af83ff4406ede84f29a666a0fcbac801",
"accumulate_root": "0x8720b97ddd6acc0f6eb66e095524038675a4e4067adc10ec39939eaefc47d842",
"work_packages": [
{
"hash": "0x016cb55eb7b84e0d495d40832c7238965baeb468932c415dc2ceffe0afb039e5",
"exports_root": "0x935f6dfef36fa06e10a9ba820f933611c05c06a207b07141fe8d87465870c11c"
},
{
"hash": "0x76bcb24901299c331f0ca7342f4874f19b213ee72df613d50699e7e25edb82a6",
"exports_root": "0xc825d16b7325ca90287123bd149d47843c999ce686ed51eaf8592dd2759272e3"
}
]
},
"pre_state": {
"beta": []
},
"output": null,
"post_state": {
"beta": [
{
"header_hash": "0x530ef4636fedd498e99c7601581271894a53e965e901e8fa49581e525f165dae",
"mmr": {
"peaks": [
"0x8720b97ddd6acc0f6eb66e095524038675a4e4067adc10ec39939eaefc47d842"
]
},
"state_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reported": [
{
"hash": "0x016cb55eb7b84e0d495d40832c7238965baeb468932c415dc2ceffe0afb039e5",
"exports_root": "0x935f6dfef36fa06e10a9ba820f933611c05c06a207b07141fe8d87465870c11c"
},
{
"hash": "0x76bcb24901299c331f0ca7342f4874f19b213ee72df613d50699e7e25edb82a6",
"exports_root": "0xc825d16b7325ca90287123bd149d47843c999ce686ed51eaf8592dd2759272e3"
}
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"input": {
"header_hash": "0x241d129c6edc2114e6dfba7d556f7f7c66399b55ceec3078a53d44c752ba7e9a",
"parent_state_root": "0x1831dde64e40bfd8639c2d122e5ac00fe133c48cd16e1621ca6d5cf0b8e10d3b",
"accumulate_root": "0x7507515a48439dc58bc318c48a120b656136699f42bfd2bd45473becba53462d",
"work_packages": [
{
"hash": "0x3cc8d8c94e7b3ee01e678c63fd6b5db894fc807dff7fe10a11ab41e70194894d",
"exports_root": "0xc0edfe377d20b9f4ed7d9df9511ef904c87e24467364f0f7f75f20cfe90dd8fb"
}
]
},
"pre_state": {
"beta": [
{
"header_hash": "0x530ef4636fedd498e99c7601581271894a53e965e901e8fa49581e525f165dae",
"mmr": {
"peaks": [
"0x8720b97ddd6acc0f6eb66e095524038675a4e4067adc10ec39939eaefc47d842"
]
},
"state_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reported": [
{
"hash": "0x016cb55eb7b84e0d495d40832c7238965baeb468932c415dc2ceffe0afb039e5",
"exports_root": "0x935f6dfef36fa06e10a9ba820f933611c05c06a207b07141fe8d87465870c11c"
},
{
"hash": "0x76bcb24901299c331f0ca7342f4874f19b213ee72df613d50699e7e25edb82a6",
"exports_root": "0xc825d16b7325ca90287123bd149d47843c999ce686ed51eaf8592dd2759272e3"
}
]
}
]
},
"output": null,
"post_state": {
"beta": [
{
"header_hash": "0x530ef4636fedd498e99c7601581271894a53e965e901e8fa49581e525f165dae",
"mmr": {
"peaks": [
"0x8720b97ddd6acc0f6eb66e095524038675a4e4067adc10ec39939eaefc47d842"
]
},
"state_root": "0x1831dde64e40bfd8639c2d122e5ac00fe133c48cd16e1621ca6d5cf0b8e10d3b",
"reported": [
{
"hash": "0x016cb55eb7b84e0d495d40832c7238965baeb468932c415dc2ceffe0afb039e5",
"exports_root": "0x935f6dfef36fa06e10a9ba820f933611c05c06a207b07141fe8d87465870c11c"
},
{
"hash": "0x76bcb24901299c331f0ca7342f4874f19b213ee72df613d50699e7e25edb82a6",
"exports_root": "0xc825d16b7325ca90287123bd149d47843c999ce686ed51eaf8592dd2759272e3"
}
]
},
{
"header_hash": "0x241d129c6edc2114e6dfba7d556f7f7c66399b55ceec3078a53d44c752ba7e9a",
"mmr": {
"peaks": [
null,
"0x7076c31882a5953e097aef8378969945e72807c4705e53a0c5aacc9176f0d56b"
]
},
"state_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
"reported": [
{
"hash": "0x3cc8d8c94e7b3ee01e678c63fd6b5db894fc807dff7fe10a11ab41e70194894d",
"exports_root": "0xc0edfe377d20b9f4ed7d9df9511ef904c87e24467364f0f7f75f20cfe90dd8fb"
}
]
}
]
}
}
Loading
Loading