go-blockchain/chain/chain_test.go
Claude 89f5f0ebdf
feat(chain): transaction, key image, and output index operations
Co-Authored-By: Charon <charon@lethean.io>
2026-02-20 21:49:15 +00:00

245 lines
5.2 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 (
"testing"
store "forge.lthn.ai/core/go-store"
"forge.lthn.ai/core/go-blockchain/types"
"forge.lthn.ai/core/go-blockchain/wire"
)
func newTestChain(t *testing.T) *Chain {
t.Helper()
s, err := store.New(":memory:")
if err != nil {
t.Fatalf("store.New: %v", err)
}
t.Cleanup(func() { s.Close() })
return New(s)
}
// testCoinbaseTx returns a minimal v1 coinbase transaction that round-trips
// cleanly through the wire encoder/decoder.
func testCoinbaseTx(height uint64) types.Transaction {
return types.Transaction{
Version: 1,
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
Vout: []types.TxOutput{types.TxOutputBare{
Amount: 1000000,
Target: types.TxOutToKey{Key: types.PublicKey{0x01}},
}},
Extra: wire.EncodeVarint(0),
Attachment: wire.EncodeVarint(0),
}
}
func TestChain_Height_Empty(t *testing.T) {
c := newTestChain(t)
h, err := c.Height()
if err != nil {
t.Fatalf("Height: %v", err)
}
if h != 0 {
t.Errorf("height: got %d, want 0", h)
}
}
func TestChain_PutGetBlock_Good(t *testing.T) {
c := newTestChain(t)
blk := &types.Block{
BlockHeader: types.BlockHeader{
MajorVersion: 1,
Timestamp: 1770897600,
},
MinerTx: testCoinbaseTx(0),
}
meta := &BlockMeta{
Hash: types.Hash{0xab, 0xcd},
Height: 0,
Timestamp: 1770897600,
Difficulty: 1,
}
if err := c.PutBlock(blk, meta); err != nil {
t.Fatalf("PutBlock: %v", err)
}
h, err := c.Height()
if err != nil {
t.Fatalf("Height: %v", err)
}
if h != 1 {
t.Errorf("height: got %d, want 1", h)
}
gotBlk, gotMeta, err := c.GetBlockByHeight(0)
if err != nil {
t.Fatalf("GetBlockByHeight: %v", err)
}
if gotBlk.MajorVersion != 1 {
t.Errorf("major_version: got %d, want 1", gotBlk.MajorVersion)
}
if gotMeta.Hash != meta.Hash {
t.Errorf("hash mismatch")
}
gotBlk2, gotMeta2, err := c.GetBlockByHash(meta.Hash)
if err != nil {
t.Fatalf("GetBlockByHash: %v", err)
}
if gotBlk2.Timestamp != blk.Timestamp {
t.Errorf("timestamp mismatch")
}
if gotMeta2.Height != 0 {
t.Errorf("height: got %d, want 0", gotMeta2.Height)
}
}
func TestChain_TopBlock_Good(t *testing.T) {
c := newTestChain(t)
for i := uint64(0); i < 3; i++ {
blk := &types.Block{
BlockHeader: types.BlockHeader{
MajorVersion: 1,
Timestamp: 1770897600 + i*120,
},
MinerTx: testCoinbaseTx(i),
}
meta := &BlockMeta{
Hash: types.Hash{byte(i)},
Height: i,
}
if err := c.PutBlock(blk, meta); err != nil {
t.Fatalf("PutBlock(%d): %v", i, err)
}
}
_, topMeta, err := c.TopBlock()
if err != nil {
t.Fatalf("TopBlock: %v", err)
}
if topMeta.Height != 2 {
t.Errorf("top height: got %d, want 2", topMeta.Height)
}
}
func TestChain_PutGetTransaction_Good(t *testing.T) {
c := newTestChain(t)
tx := &types.Transaction{
Version: 1,
Vin: []types.TxInput{
types.TxInputToKey{
Amount: 1000000000000,
KeyImage: types.KeyImage{0x01},
EtcDetails: wire.EncodeVarint(0),
},
},
Vout: []types.TxOutput{
types.TxOutputBare{
Amount: 900000000000,
Target: types.TxOutToKey{Key: types.PublicKey{0x02}},
},
},
Extra: wire.EncodeVarint(0),
Attachment: wire.EncodeVarint(0),
}
meta := &TxMeta{
KeeperBlock: 5,
GlobalOutputIndexes: []uint64{42},
}
txHash := types.Hash{0xde, 0xad}
if err := c.PutTransaction(txHash, tx, meta); err != nil {
t.Fatalf("PutTransaction: %v", err)
}
if !c.HasTransaction(txHash) {
t.Error("HasTransaction: got false, want true")
}
gotTx, gotMeta, err := c.GetTransaction(txHash)
if err != nil {
t.Fatalf("GetTransaction: %v", err)
}
if gotTx.Version != 1 {
t.Errorf("version: got %d, want 1", gotTx.Version)
}
if gotMeta.KeeperBlock != 5 {
t.Errorf("keeper_block: got %d, want 5", gotMeta.KeeperBlock)
}
}
func TestChain_KeyImage_Good(t *testing.T) {
c := newTestChain(t)
ki := types.KeyImage{0xaa, 0xbb}
spent, err := c.IsSpent(ki)
if err != nil {
t.Fatalf("IsSpent: %v", err)
}
if spent {
t.Error("IsSpent: got true before marking")
}
if err := c.MarkSpent(ki, 10); err != nil {
t.Fatalf("MarkSpent: %v", err)
}
spent, err = c.IsSpent(ki)
if err != nil {
t.Fatalf("IsSpent: %v", err)
}
if !spent {
t.Error("IsSpent: got false after marking")
}
}
func TestChain_OutputIndex_Good(t *testing.T) {
c := newTestChain(t)
txID := types.Hash{0x01}
gidx0, err := c.PutOutput(1000000000000, txID, 0)
if err != nil {
t.Fatalf("PutOutput(0): %v", err)
}
if gidx0 != 0 {
t.Errorf("gindex: got %d, want 0", gidx0)
}
gidx1, err := c.PutOutput(1000000000000, txID, 1)
if err != nil {
t.Fatalf("PutOutput(1): %v", err)
}
if gidx1 != 1 {
t.Errorf("gindex: got %d, want 1", gidx1)
}
count, err := c.OutputCount(1000000000000)
if err != nil {
t.Fatalf("OutputCount: %v", err)
}
if count != 2 {
t.Errorf("count: got %d, want 2", count)
}
gotTxID, gotOutNo, err := c.GetOutput(1000000000000, 0)
if err != nil {
t.Fatalf("GetOutput: %v", err)
}
if gotTxID != txID {
t.Errorf("tx_id mismatch")
}
if gotOutNo != 0 {
t.Errorf("out_no: got %d, want 0", gotOutNo)
}
}