P2PSync() runs the REQUEST_CHAIN / REQUEST_GET_OBJECTS loop against a P2PConnection interface. Reuses processBlockBlobs() for shared validation logic. Co-Authored-By: Charon <charon@lethean.io>
140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
|
//
|
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package chain
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
store "forge.lthn.ai/core/go-store"
|
|
"forge.lthn.ai/core/go-blockchain/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// mockP2PConn implements P2PConnection for testing.
|
|
type mockP2PConn struct {
|
|
peerHeight uint64
|
|
// blocks maps hash -> (blockBlob, txBlobs)
|
|
blocks map[string]struct {
|
|
blockBlob []byte
|
|
txBlobs [][]byte
|
|
}
|
|
chainHashes [][]byte
|
|
|
|
// requestChainErr, if set, is returned by RequestChain.
|
|
requestChainErr error
|
|
// requestObjectsErr, if set, is returned by RequestObjects.
|
|
requestObjectsErr error
|
|
}
|
|
|
|
func (m *mockP2PConn) PeerHeight() uint64 { return m.peerHeight }
|
|
|
|
func (m *mockP2PConn) RequestChain(blockIDs [][]byte) (startHeight uint64, hashes [][]byte, err error) {
|
|
if m.requestChainErr != nil {
|
|
return 0, nil, m.requestChainErr
|
|
}
|
|
return 0, m.chainHashes, nil
|
|
}
|
|
|
|
func (m *mockP2PConn) RequestObjects(blockHashes [][]byte) ([]BlockBlobEntry, error) {
|
|
if m.requestObjectsErr != nil {
|
|
return nil, m.requestObjectsErr
|
|
}
|
|
var entries []BlockBlobEntry
|
|
for _, h := range blockHashes {
|
|
key := string(h)
|
|
if blk, ok := m.blocks[key]; ok {
|
|
entries = append(entries, BlockBlobEntry{
|
|
Block: blk.blockBlob,
|
|
Txs: blk.txBlobs,
|
|
})
|
|
}
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
func TestP2PSync_EmptyChain(t *testing.T) {
|
|
// Test that P2PSync with a mock that has no blocks is a no-op.
|
|
s, err := store.New(":memory:")
|
|
require.NoError(t, err)
|
|
defer s.Close()
|
|
|
|
c := New(s)
|
|
mock := &mockP2PConn{peerHeight: 0}
|
|
|
|
opts := SyncOptions{Forks: config.TestnetForks}
|
|
err = c.P2PSync(context.Background(), mock, opts)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestP2PSync_ContextCancellation(t *testing.T) {
|
|
s, err := store.New(":memory:")
|
|
require.NoError(t, err)
|
|
defer s.Close()
|
|
|
|
c := New(s)
|
|
mock := &mockP2PConn{peerHeight: 100} // claims 100 blocks but returns none
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel immediately
|
|
|
|
opts := SyncOptions{Forks: config.TestnetForks}
|
|
err = c.P2PSync(ctx, mock, opts)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
}
|
|
|
|
func TestP2PSync_NoBlockIDs(t *testing.T) {
|
|
// Peer claims a height but returns no block IDs from RequestChain.
|
|
s, err := store.New(":memory:")
|
|
require.NoError(t, err)
|
|
defer s.Close()
|
|
|
|
c := New(s)
|
|
mock := &mockP2PConn{
|
|
peerHeight: 10,
|
|
chainHashes: nil, // empty response
|
|
}
|
|
|
|
opts := SyncOptions{Forks: config.TestnetForks}
|
|
err = c.P2PSync(context.Background(), mock, opts)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestP2PSync_RequestChainError(t *testing.T) {
|
|
s, err := store.New(":memory:")
|
|
require.NoError(t, err)
|
|
defer s.Close()
|
|
|
|
c := New(s)
|
|
mock := &mockP2PConn{
|
|
peerHeight: 10,
|
|
requestChainErr: fmt.Errorf("connection reset"),
|
|
}
|
|
|
|
opts := SyncOptions{Forks: config.TestnetForks}
|
|
err = c.P2PSync(context.Background(), mock, opts)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "request chain")
|
|
}
|
|
|
|
func TestP2PSync_RequestObjectsError(t *testing.T) {
|
|
s, err := store.New(":memory:")
|
|
require.NoError(t, err)
|
|
defer s.Close()
|
|
|
|
c := New(s)
|
|
mock := &mockP2PConn{
|
|
peerHeight: 10,
|
|
chainHashes: [][]byte{{0x01}},
|
|
requestObjectsErr: fmt.Errorf("timeout"),
|
|
}
|
|
|
|
opts := SyncOptions{Forks: config.TestnetForks}
|
|
err = c.P2PSync(context.Background(), mock, opts)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "request objects")
|
|
}
|