271 lines
6.5 KiB
Go
271 lines
6.5 KiB
Go
package cargen
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
bin "github.com/gagliardetto/binary"
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/go-logr/logr/testr"
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipld/go-car"
|
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
"github.com/multiformats/go-multicodec"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.firedancer.io/radiance/pkg/blockstore"
|
|
"go.firedancer.io/radiance/pkg/shred"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
// mockBlockWalk is a mock implementation of blockstore.BlockWalkI.
|
|
type mockBlockWalk struct {
|
|
queue []*blockstore.SlotMeta
|
|
entries map[*blockstore.SlotMeta][][]shred.Entry
|
|
staged [][]shred.Entry
|
|
slot uint64
|
|
left int64
|
|
}
|
|
|
|
func newMockBlockWalk() *mockBlockWalk {
|
|
return &mockBlockWalk{
|
|
queue: nil,
|
|
entries: make(map[*blockstore.SlotMeta][][]shred.Entry),
|
|
staged: nil,
|
|
}
|
|
}
|
|
|
|
func (m *mockBlockWalk) append(meta *blockstore.SlotMeta, entries [][]shred.Entry) {
|
|
m.queue = append(m.queue, meta)
|
|
m.entries[meta] = entries
|
|
}
|
|
|
|
func (m *mockBlockWalk) Seek(slot uint64) (ok bool) {
|
|
for len(m.queue) > 0 && m.queue[len(m.queue)-1].Slot < slot {
|
|
delete(m.entries, m.queue[0])
|
|
m.queue = m.queue[1:]
|
|
}
|
|
return len(m.queue) > 0
|
|
}
|
|
|
|
func (m *mockBlockWalk) SlotsAvailable() uint64 {
|
|
if m.left <= 0 {
|
|
return 0
|
|
}
|
|
return uint64(m.left)
|
|
}
|
|
|
|
func (m *mockBlockWalk) Next() (meta *blockstore.SlotMeta, ok bool) {
|
|
if len(m.queue) == 0 {
|
|
m.left = 0
|
|
return nil, false
|
|
}
|
|
|
|
meta = m.queue[0]
|
|
m.queue = m.queue[1:]
|
|
m.left -= int64(meta.Slot) - int64(m.slot)
|
|
|
|
m.staged = m.entries[meta]
|
|
delete(m.entries, meta)
|
|
m.slot = meta.Slot
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (m *mockBlockWalk) Entries(*blockstore.SlotMeta) ([][]shred.Entry, error) {
|
|
return m.staged, nil
|
|
}
|
|
|
|
func (m *mockBlockWalk) Close() {
|
|
m.queue = nil
|
|
m.entries = nil
|
|
m.staged = nil
|
|
}
|
|
|
|
func TestGen_Empty(t *testing.T) {
|
|
walk := newMockBlockWalk()
|
|
dir := t.TempDir()
|
|
worker, err := NewWorker(dir, 0, walk)
|
|
assert.EqualError(t, err, "slot 0 not available in any DB")
|
|
assert.Nil(t, worker)
|
|
}
|
|
|
|
func TestGen_Split(t *testing.T) {
|
|
klog.SetLogger(testr.New(t))
|
|
t.Cleanup(klog.ClearLogger)
|
|
|
|
walk := newMockBlockWalk()
|
|
walk.left = 432000
|
|
walk.append(
|
|
&blockstore.SlotMeta{Slot: 432000, EntryEndIndexes: []uint32{0}},
|
|
[][]shred.Entry{
|
|
{
|
|
{
|
|
Txns: []solana.Transaction{
|
|
{
|
|
Signatures: make([]solana.Signature, 1),
|
|
Message: solana.Message{
|
|
AccountKeys: make([]solana.PublicKey, 3),
|
|
Header: solana.MessageHeader{},
|
|
RecentBlockhash: solana.Hash{},
|
|
Instructions: []solana.CompiledInstruction{
|
|
{
|
|
Accounts: make([]uint16, 2),
|
|
Data: make(solana.Base58, 64),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
walk.append(
|
|
&blockstore.SlotMeta{Slot: 432002, EntryEndIndexes: []uint32{0, 1}},
|
|
[][]shred.Entry{
|
|
{
|
|
{
|
|
Txns: []solana.Transaction{
|
|
{
|
|
Signatures: make([]solana.Signature, 1),
|
|
Message: solana.Message{
|
|
AccountKeys: make([]solana.PublicKey, 1),
|
|
Header: solana.MessageHeader{},
|
|
RecentBlockhash: solana.Hash{},
|
|
Instructions: []solana.CompiledInstruction{
|
|
{
|
|
Accounts: make([]uint16, 1),
|
|
Data: make(solana.Base58, 20),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
{
|
|
Txns: []solana.Transaction{
|
|
{
|
|
Signatures: make([]solana.Signature, 1),
|
|
Message: solana.Message{
|
|
AccountKeys: make([]solana.PublicKey, 1),
|
|
Header: solana.MessageHeader{},
|
|
RecentBlockhash: solana.Hash{},
|
|
Instructions: []solana.CompiledInstruction{
|
|
{
|
|
Accounts: make([]uint16, 1),
|
|
Data: make(solana.Base58, 20),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
dir := t.TempDir()
|
|
worker, err := NewWorker(dir, 1, walk)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, worker)
|
|
|
|
worker.CARSize = 1024
|
|
require.NoError(t, worker.Run(context.Background()))
|
|
|
|
entries, err := os.ReadDir(dir)
|
|
require.NoError(t, err)
|
|
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return strings.Compare(entries[i].Name(), entries[j].Name()) < 0
|
|
})
|
|
assert.Len(t, entries, 2)
|
|
|
|
cases := map[string]struct {
|
|
size int64
|
|
cids []string
|
|
}{
|
|
"ledger-e1-s432000.car": {
|
|
size: 534,
|
|
cids: []string{
|
|
"bafkreicloicbw5yk5bpkthpwgnxrjy4iwqiitbvxs52tbkxm3fqcmgvl7a", // KindTx
|
|
"bafyreiawzny3vxq6bir23qjai6fcujyiciqz7iftnibrfzbuvtoy7fvqtq", // KindEntry
|
|
"bafyreiaihhxk2rfo5lgilus3wp3mqglv2do3zgtfqpp5zfygrpzdbez3am", // KindBlock
|
|
},
|
|
},
|
|
"ledger-e1-s432002.car": {
|
|
size: 782,
|
|
cids: []string{
|
|
"bafkreihgemfmup2imylvtyp2wj3fymakbx2bfyi2rlzxxq4xv3ycgmc3da", // KindTx
|
|
"bafyreih5wmt3ly25phouneamyxg5fs4uu3ma6kdvgwfregnmjsibkuipeq", // KindEntry
|
|
"bafkreihgemfmup2imylvtyp2wj3fymakbx2bfyi2rlzxxq4xv3ycgmc3da", // KindTx
|
|
"bafyreih5wmt3ly25phouneamyxg5fs4uu3ma6kdvgwfregnmjsibkuipeq", // KindEntry
|
|
"bafyreibqnqnmnhunzwcopnc7srlvq5p5bbgex2xspb6ayzf265rcwtaiie", // KindBlock
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
filePath := filepath.Join(dir, name)
|
|
t.Run("Parse_"+name, func(t *testing.T) {
|
|
// match file size
|
|
info, err := os.Stat(filePath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.size, info.Size())
|
|
|
|
// ensure CARs decode
|
|
f, err := os.Open(filepath.Join(dir, name))
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
rd, err := car.NewCarReader(f)
|
|
require.NoError(t, err)
|
|
|
|
var cids []cid.Cid
|
|
for {
|
|
block, err := rd.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
require.NoError(t, err)
|
|
cid := block.Cid()
|
|
cidType := cid.Type()
|
|
t.Logf("CID=%s Multicodec=%#x", cid, cidType)
|
|
switch multicodec.Code(cidType) {
|
|
case multicodec.Raw:
|
|
var tx solana.Transaction
|
|
require.NoError(t, bin.UnmarshalBin(&tx, block.RawData()))
|
|
t.Logf(" Txn: %s", &tx)
|
|
case multicodec.DagCbor:
|
|
decodeOpt := dagcbor.DecodeOptions{
|
|
AllowLinks: true,
|
|
}
|
|
builder := basicnode.Prototype.Any.NewBuilder()
|
|
require.NoError(t, decodeOpt.Decode(builder, bytes.NewReader(block.RawData())))
|
|
node := builder.Build()
|
|
t.Logf(" Entry: %s", node.Kind())
|
|
default:
|
|
panic("Unexpected entry")
|
|
}
|
|
cids = append(cids, cid)
|
|
}
|
|
|
|
// match CIDs
|
|
cidStrs := make([]string, len(cids))
|
|
for i, c := range cids {
|
|
cidStrs[i] = c.String()
|
|
}
|
|
assert.Equal(t, tc.cids, cidStrs)
|
|
})
|
|
}
|
|
}
|