Skip to content

Commit

Permalink
Add recent history integration test (#138)
Browse files Browse the repository at this point in the history
- Separate core logic for updating recent history for simpler
  integration tests.
- Add recent history test vectors, and integration test.
- Fix bug in MMR. We shouldn't always hash the input.
  • Loading branch information
greywolve authored Nov 26, 2024
1 parent c79c4c7 commit be1e01c
Show file tree
Hide file tree
Showing 7 changed files with 763 additions and 15 deletions.
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

0 comments on commit be1e01c

Please sign in to comment.