mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-07 20:43:48 +00:00
Add a new abstraction called span store in bor consensus. This makes the header verification stateless as instead of fetching validator set (of a span) from genesis contract, we rely on span from heimdall itself for validator set. This doesn't change any assumptions for consensus as the data in genesis contracts is written by fetching span details from heimdall in the first place. This helps us with multiple things such as header verification for snap sync, validating producer set against header's extra data, and using heimdall as a source of truth instead of making a contract call to genesis contract which generally leads to issues when importing side chains (as we need to use the side chain state and not canonical). Span store is a minimal cache which maintains last 10 spans in memory. It provide simple read methods via span id and block number so that we can use it in consensus.
220 lines
8.6 KiB
Go
220 lines
8.6 KiB
Go
package bor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
|
|
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint"
|
|
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/milestone"
|
|
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type MockHeimdallClient struct {
|
|
}
|
|
|
|
func (h *MockHeimdallClient) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) {
|
|
// Throw error for span id 100
|
|
if spanID == 100 {
|
|
return nil, fmt.Errorf("unable to fetch span")
|
|
}
|
|
|
|
// For everything else, return hardcoded span assuming length 6400 (except for span 0)
|
|
if spanID == 0 {
|
|
return &span.HeimdallSpan{
|
|
Span: span.Span{
|
|
ID: 0,
|
|
StartBlock: 0,
|
|
EndBlock: 255,
|
|
},
|
|
}, nil
|
|
} else {
|
|
return &span.HeimdallSpan{
|
|
Span: span.Span{
|
|
ID: spanID,
|
|
StartBlock: 6400*(spanID-1) + 256,
|
|
EndBlock: 6400*spanID + 255,
|
|
},
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func TestSpanStore_SpanById(t *testing.T) {
|
|
spanStore := NewSpanStore(&MockHeimdallClient{}, nil, "1337")
|
|
ctx := context.Background()
|
|
|
|
type Testcase struct {
|
|
id uint64
|
|
startBlock uint64
|
|
endBlock uint64
|
|
}
|
|
|
|
testcases := []Testcase{
|
|
{id: 0, startBlock: 0, endBlock: 255},
|
|
{id: 1, startBlock: 256, endBlock: 6655},
|
|
{id: 2, startBlock: 6656, endBlock: 13055},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run("", func(t *testing.T) {
|
|
span, err := spanStore.spanById(ctx, tc.id)
|
|
require.NoError(t, err, "err in spanById for id=%d", tc.id)
|
|
require.Equal(t, tc.id, span.ID, "invalid id in spanById for id=%d", tc.id)
|
|
require.Equal(t, tc.startBlock, span.StartBlock, "invalid start block in spanById for id=%d", tc.id)
|
|
require.Equal(t, tc.endBlock, span.EndBlock, "invalid end block in spanById for id=%d", tc.id)
|
|
})
|
|
}
|
|
|
|
// Ensure cache is updated
|
|
keys := spanStore.store.Keys()
|
|
require.Len(t, keys, 3, "invalid length of keys in span store")
|
|
|
|
// Ensure latest known span id is updated
|
|
require.Equal(t, uint64(2), spanStore.latestKnownSpanId, "invalid latest known span id in span store")
|
|
|
|
// Ask for a few more spans
|
|
for i := spanStore.latestKnownSpanId; i <= 20; i++ {
|
|
_, err := spanStore.spanById(ctx, i)
|
|
require.NoError(t, err, "err in spanById for id=%d", i)
|
|
}
|
|
|
|
// Ensure cache is updated
|
|
keys = spanStore.store.Keys()
|
|
require.Len(t, keys, 10, "invalid length of keys in span store")
|
|
|
|
// Ensure latest known span id is updated
|
|
require.Equal(t, uint64(20), spanStore.latestKnownSpanId, "invalid latest known span id in span store")
|
|
|
|
// Ensure we're still able to fetch old spans even though they're evicted from cache
|
|
span, err := spanStore.spanById(ctx, 0)
|
|
require.NoError(t, err, "err in spanById after eviction for id=0")
|
|
require.Equal(t, uint64(0), span.ID, "invalid id in spanById after eviction for id=0")
|
|
require.Equal(t, uint64(0), span.StartBlock, "invalid start block in spanById after eviction for id=0")
|
|
require.Equal(t, uint64(255), span.EndBlock, "invalid end block in spanById after eviction for id=0")
|
|
|
|
// Try fetching span 100 and ensure error is handled
|
|
span, err = spanStore.spanById(ctx, 100)
|
|
require.Error(t, err, "expected error in spanById for id=100")
|
|
require.Nil(t, span, "expected nil span in spanById for id=100")
|
|
|
|
// Ensure latest known span is still the old one
|
|
require.Equal(t, uint64(20), spanStore.latestKnownSpanId, "invalid latest known span id in span store")
|
|
}
|
|
|
|
func TestSpanStore_SpanByBlockNumber(t *testing.T) {
|
|
spanStore := NewSpanStore(&MockHeimdallClient{}, nil, "1337")
|
|
ctx := context.Background()
|
|
|
|
type Testcase struct {
|
|
blockNumber uint64
|
|
id uint64
|
|
startBlock uint64
|
|
endBlock uint64
|
|
}
|
|
|
|
// Insert a few spans
|
|
for i := spanStore.latestKnownSpanId; i < 3; i++ {
|
|
_, err := spanStore.spanById(ctx, i)
|
|
require.NoError(t, err, "err in spanById for id=%d", i)
|
|
}
|
|
|
|
// Ensure cache is updated
|
|
keys := spanStore.store.Keys()
|
|
require.Len(t, keys, 3, "invalid length of keys in span store")
|
|
|
|
// Ensure latest known span id is updated
|
|
require.Equal(t, uint64(2), spanStore.latestKnownSpanId, "invalid latest known span id in span store")
|
|
|
|
// Ask for current and past spans via block number
|
|
testcases := []Testcase{
|
|
{blockNumber: 0, id: 0, startBlock: 0, endBlock: 255},
|
|
{blockNumber: 1, id: 0, startBlock: 0, endBlock: 255},
|
|
{blockNumber: 255, id: 0, startBlock: 0, endBlock: 255},
|
|
{blockNumber: 256, id: 1, startBlock: 256, endBlock: 6655},
|
|
{blockNumber: 257, id: 1, startBlock: 256, endBlock: 6655},
|
|
{blockNumber: 6000, id: 1, startBlock: 256, endBlock: 6655},
|
|
{blockNumber: 6655, id: 1, startBlock: 256, endBlock: 6655},
|
|
{blockNumber: 6656, id: 2, startBlock: 6656, endBlock: 13055},
|
|
{blockNumber: 10000, id: 2, startBlock: 6656, endBlock: 13055},
|
|
{blockNumber: 13055, id: 2, startBlock: 6656, endBlock: 13055},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run("", func(t *testing.T) {
|
|
span, err := spanStore.spanByBlockNumber(ctx, tc.blockNumber)
|
|
require.NoError(t, err, "err in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.id, span.ID, "invalid id in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.startBlock, span.StartBlock, "invalid start block in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.endBlock, span.EndBlock, "invalid end block in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
})
|
|
}
|
|
|
|
// Insert a few more spans to trigger eviction
|
|
for i := spanStore.latestKnownSpanId; i <= 20; i++ {
|
|
_, err := spanStore.spanById(ctx, i)
|
|
require.NoError(t, err, "err in spanById for id=%d", i)
|
|
}
|
|
|
|
// Ensure cache is updated
|
|
keys = spanStore.store.Keys()
|
|
require.Len(t, keys, 10, "invalid length of keys in span store")
|
|
|
|
// Ensure latest known span id is updated
|
|
require.Equal(t, uint64(20), spanStore.latestKnownSpanId, "invalid latest known span id in span store")
|
|
|
|
// Ask for current and past spans
|
|
testcases = append(testcases, Testcase{blockNumber: 57856, id: 10, startBlock: 57856, endBlock: 64255})
|
|
testcases = append(testcases, Testcase{blockNumber: 60000, id: 10, startBlock: 57856, endBlock: 64255})
|
|
testcases = append(testcases, Testcase{blockNumber: 64255, id: 10, startBlock: 57856, endBlock: 64255})
|
|
testcases = append(testcases, Testcase{blockNumber: 121856, id: 20, startBlock: 121856, endBlock: 128255})
|
|
testcases = append(testcases, Testcase{blockNumber: 122000, id: 20, startBlock: 121856, endBlock: 128255})
|
|
testcases = append(testcases, Testcase{blockNumber: 128255, id: 20, startBlock: 121856, endBlock: 128255})
|
|
|
|
for _, tc := range testcases {
|
|
t.Run("", func(t *testing.T) {
|
|
span, err := spanStore.spanByBlockNumber(ctx, tc.blockNumber)
|
|
require.NoError(t, err, "err in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.id, span.ID, "invalid id in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.startBlock, span.StartBlock, "invalid start block in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
require.Equal(t, tc.endBlock, span.EndBlock, "invalid end block in spanByBlockNumber for block=%d", tc.blockNumber)
|
|
})
|
|
}
|
|
|
|
// Asking for a future span
|
|
span, err := spanStore.spanByBlockNumber(ctx, 128256) // block 128256 belongs to span 21 (future span)
|
|
require.NoError(t, err, "err in spanByBlockNumber for future block 128256")
|
|
require.Equal(t, uint64(21), span.ID, "invalid id in spanByBlockNumber for future block 128256")
|
|
require.Equal(t, uint64(128256), span.StartBlock, "invalid start block in spanByBlockNumber for future block 128256")
|
|
require.Equal(t, uint64(134655), span.EndBlock, "invalid end block in spanByBlockNumber for future block 128256")
|
|
}
|
|
|
|
// Irrelevant to the tests above but necessary for interface compatibility
|
|
func (h *MockHeimdallClient) StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchCheckpointCount(ctx context.Context) (int64, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchMilestone(ctx context.Context) (*milestone.Milestone, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchMilestoneCount(ctx context.Context) (int64, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchNoAckMilestone(ctx context.Context, milestoneID string) error {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchLastNoAckMilestone(ctx context.Context) (string, error) {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) FetchMilestoneID(ctx context.Context, milestoneID string) error {
|
|
panic("implement me")
|
|
}
|
|
func (h *MockHeimdallClient) Close() {
|
|
panic("implement me")
|
|
}
|