Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions kernel/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package kernel
*/
import "C"
import (
"iter"
"unsafe"
)

Expand Down Expand Up @@ -87,3 +88,74 @@ func (b *Block) GetTransactionAt(index uint64) (*TransactionView, error) {
ptr := C.btck_block_get_transaction_at((*C.btck_Block)(b.ptr), C.size_t(index))
return newTransactionView(check(ptr)), nil
}

// Transactions returns an iterator over all transactions in the block.
//
// The returned transactions are non-owned views that depend on the lifetime of this Block.
//
// Example usage:
//
// for tx := range block.Transactions() {
// // Process transaction
// }
func (b *Block) Transactions() iter.Seq[*TransactionView] {
return func(yield func(*TransactionView) bool) {
b.iterTransactions(0, b.CountTransactions(), yield)
}
}

// TransactionsRange returns an iterator over a range of transactions in the block.
//
// Parameters:
// - from: Starting index (inclusive)
// - to: Ending index (exclusive)
//
// The returned transactions are non-owned views that depend on the lifetime of this Block.
// Safe for out-of-bounds arguments: 'to' is clamped to the count,
// and an invalid range (from >= to) yields an empty iterator.
//
// Example usage:
//
// for tx := range block.TransactionsRange(0, 5) {
// // Process transactions 0-4
// }
func (b *Block) TransactionsRange(from, to uint64) iter.Seq[*TransactionView] {
return func(yield func(*TransactionView) bool) {
if count := b.CountTransactions(); to > count {
to = count
}
b.iterTransactions(from, to, yield)
}
}

// TransactionsFrom returns an iterator over transactions starting from the given index.
//
// Parameters:
// - from: Starting index (inclusive)
//
// The returned transactions are non-owned views that depend on the lifetime of this Block.
// If from is beyond the transaction count, returns an empty iterator.
//
// Example usage:
//
// for tx := range block.TransactionsFrom(5) {
// // Process transactions from index 5 to the end
// }
func (b *Block) TransactionsFrom(from uint64) iter.Seq[*TransactionView] {
return func(yield func(*TransactionView) bool) {
b.iterTransactions(from, b.CountTransactions(), yield)
}
}

// iterTransactions is a helper that iterates over transactions in [from, to).
func (b *Block) iterTransactions(from, to uint64, yield func(*TransactionView) bool) {
for i := from; i < to; i++ {
tx, err := b.GetTransactionAt(i)
if err != nil {
panic(err)
}
if !yield(tx) {
return
}
}
}
72 changes: 72 additions & 0 deletions kernel/block_spent_outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package kernel
*/
import "C"
import (
"iter"
"unsafe"
)

Expand Down Expand Up @@ -64,3 +65,74 @@ func (bso *BlockSpentOutputs) GetTransactionSpentOutputsAt(index uint64) (*Trans
func (bso *BlockSpentOutputs) Copy() *BlockSpentOutputs {
return newBlockSpentOutputs((*C.btck_BlockSpentOutputs)(bso.ptr), false)
}

// TransactionsSpentOutputs returns an iterator over all transaction spent outputs in the block.
//
// The returned transaction spent outputs are non-owned views that depend on the lifetime of this BlockSpentOutputs.
//
// Example usage:
//
// for txSpentOutputs := range blockSpentOutputs.TransactionsSpentOutputs() {
// // Process transaction spent outputs
// }
func (bso *BlockSpentOutputs) TransactionsSpentOutputs() iter.Seq[*TransactionSpentOutputsView] {
return func(yield func(*TransactionSpentOutputsView) bool) {
bso.iterTransactionsSpentOutputs(0, bso.Count(), yield)
}
}

// TransactionsSpentOutputsRange returns an iterator over a range of transaction spent outputs in the block.
//
// Parameters:
// - from: Starting index (inclusive)
// - to: Ending index (exclusive)
//
// The returned transaction spent outputs are non-owned views that depend on the lifetime of this BlockSpentOutputs.
// Safe for out-of-bounds arguments: 'to' is clamped to the count,
// and an invalid range (from >= to) yields an empty iterator.
//
// Example usage:
//
// for txSpentOutputs := range blockSpentOutputs.TransactionsSpentOutputsRange(0, 5) {
// // Process transaction spent outputs 0-4
// }
func (bso *BlockSpentOutputs) TransactionsSpentOutputsRange(from, to uint64) iter.Seq[*TransactionSpentOutputsView] {
return func(yield func(*TransactionSpentOutputsView) bool) {
if count := bso.Count(); to > count {
to = count
}
bso.iterTransactionsSpentOutputs(from, to, yield)
}
}

// TransactionsSpentOutputsFrom returns an iterator over transaction spent outputs starting from the given index.
//
// Parameters:
// - from: Starting index (inclusive)
//
// The returned transaction spent outputs are non-owned views that depend on the lifetime of this BlockSpentOutputs.
// If from is beyond the count, returns an empty iterator.
//
// Example usage:
//
// for txSpentOutputs := range blockSpentOutputs.TransactionsSpentOutputsFrom(5) {
// // Process transaction spent outputs from index 5 to the end
// }
func (bso *BlockSpentOutputs) TransactionsSpentOutputsFrom(from uint64) iter.Seq[*TransactionSpentOutputsView] {
return func(yield func(*TransactionSpentOutputsView) bool) {
bso.iterTransactionsSpentOutputs(from, bso.Count(), yield)
}
}

// iterTransactionsSpentOutputs is a helper that iterates over transaction spent outputs in [from, to).
func (bso *BlockSpentOutputs) iterTransactionsSpentOutputs(from, to uint64, yield func(*TransactionSpentOutputsView) bool) {
for i := from; i < to; i++ {
txSpentOutputs, err := bso.GetTransactionSpentOutputsAt(i)
if err != nil {
panic(err)
}
if !yield(txSpentOutputs) {
return
}
}
}
195 changes: 95 additions & 100 deletions kernel/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kernel
import (
"encoding/hex"
"errors"
"slices"
"testing"
)

Expand All @@ -14,7 +15,7 @@ func TestInvalidBlockData(t *testing.T) {
}
}

func TestBlockFromRaw(t *testing.T) {
func TestBlock(t *testing.T) {
// Complete Bitcoin mainnet genesis block (285 bytes)
genesisHex := "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
genesisBytes, err := hex.DecodeString(genesisHex)
Expand All @@ -24,108 +25,102 @@ func TestBlockFromRaw(t *testing.T) {

block, err := NewBlock(genesisBytes)
if err != nil {
t.Fatalf("NewBlockFromRaw() error = %v", err)
t.Fatalf("NewBlock() error = %v", err)
}
defer block.Destroy()

// Test getting block hash
hash := block.Hash()
defer hash.Destroy()

// Expected genesis block hash (reversed byte order for display)
expectedHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
hashBytes := hash.Bytes()
actualHashHex := hex.EncodeToString(reverseBytes(hashBytes[:]))
if actualHashHex != expectedHash {
t.Logf("Actual hash: %s", actualHashHex)
t.Logf("Expected hash: %s", expectedHash)
}

// Test getting the serialized block
data, err := block.Bytes()
if err != nil {
t.Fatalf("Block.Bytes() error = %v", err)
}

if len(data) != len(genesisBytes) {
t.Errorf("Expected data length %d, got %d", len(genesisBytes), len(data))
}

hexStr := hex.EncodeToString(data)
if hexStr != genesisHex {
t.Logf("Expected data hex: %s, got %s", genesisHex, hexStr)
}
}

func TestBlockCopy(t *testing.T) {
genesisHex := "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
genesisBytes, err := hex.DecodeString(genesisHex)
if err != nil {
t.Fatalf("Failed to decode genesis hex: %v", err)
}

block, err := NewBlock(genesisBytes)
if err != nil {
t.Fatalf("NewBlockFromRaw() error = %v", err)
}
defer block.Destroy()

// Test copying block
blockCopy := block.Copy()
if blockCopy == nil {
t.Fatal("Copied block is nil")
}
defer blockCopy.Destroy()

if blockCopy.ptr == nil {
t.Error("Copied block pointer is nil")
}
}

func TestBlockCountTransactions(t *testing.T) {
genesisHex := "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
genesisBytes, err := hex.DecodeString(genesisHex)
if err != nil {
t.Fatalf("Failed to decode genesis hex: %v", err)
}

block, err := NewBlock(genesisBytes)
if err != nil {
t.Fatalf("NewBlockFromRaw() error = %v", err)
}
defer block.Destroy()

// Test counting transactions (genesis block has 1 transaction)
txCount := block.CountTransactions()
if txCount != 1 {
t.Errorf("Expected 1 transaction, got %d", txCount)
}
}

func TestBlockGetTransactionAt(t *testing.T) {
genesisHex := "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
genesisBytes, err := hex.DecodeString(genesisHex)
if err != nil {
t.Fatalf("Failed to decode genesis hex: %v", err)
}

block, err := NewBlock(genesisBytes)
if err != nil {
t.Fatalf("NewBlockFromRaw() error = %v", err)
}
defer block.Destroy()

// Test getting transaction at index 0
tx, err := block.GetTransactionAt(0)
if err != nil {
t.Fatalf("Block.GetTransactionAt(0) error = %v", err)
}
if tx == nil {
t.Fatal("Transaction is nil")
}
if tx.ptr == nil {
t.Error("Transaction pointer is nil")
}
t.Run("Hash", func(t *testing.T) {
hash := block.Hash()
defer hash.Destroy()

// Expected genesis block hash (reversed byte order for display)
expectedHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
hashBytes := hash.Bytes()
actualHashHex := hex.EncodeToString(reverseBytes(hashBytes[:]))
if actualHashHex != expectedHash {
t.Errorf("Expected hash %s, got %s", expectedHash, actualHashHex)
}
})

t.Run("Bytes", func(t *testing.T) {
data, err := block.Bytes()
if err != nil {
t.Fatalf("Block.Bytes() error = %v", err)
}

if len(data) != len(genesisBytes) {
t.Errorf("Expected data length %d, got %d", len(genesisBytes), len(data))
}

hexStr := hex.EncodeToString(data)
if hexStr != genesisHex {
t.Errorf("Expected data hex %s, got %s", genesisHex, hexStr)
}
})

t.Run("Copy", func(t *testing.T) {
blockCopy := block.Copy()
if blockCopy == nil {
t.Fatal("Copied block is nil")
}
defer blockCopy.Destroy()

if blockCopy.ptr == nil {
t.Error("Copied block pointer is nil")
}
})

t.Run("CountTransactions", func(t *testing.T) {
// Genesis block has 1 transaction
txCount := block.CountTransactions()
if txCount != 1 {
t.Errorf("Expected 1 transaction, got %d", txCount)
}
})

t.Run("GetTransactionAt", func(t *testing.T) {
tx, err := block.GetTransactionAt(0)
if err != nil {
t.Fatalf("Block.GetTransactionAt(0) error = %v", err)
}
if tx == nil {
t.Fatal("Transaction is nil")
}
if tx.ptr == nil {
t.Error("Transaction pointer is nil")
}
})

t.Run("Transactions", func(t *testing.T) {
count := len(slices.Collect(block.Transactions()))
if count != 1 {
t.Errorf("Expected to iterate over 1 transaction, got %d", count)
}
})

t.Run("TransactionsRange", func(t *testing.T) {
count := len(slices.Collect(block.TransactionsRange(0, 1000)))
if count != 1 {
t.Errorf("Expected to iterate over 1 transaction, got %d", count)
}

count = len(slices.Collect(block.TransactionsRange(1, 2)))
if count != 0 {
t.Errorf("Expected to iterate over 0 transactions, got %d", count)
}
})

t.Run("TransactionsFrom", func(t *testing.T) {
count := len(slices.Collect(block.TransactionsFrom(0)))
if count != 1 {
t.Errorf("Expected to iterate over 1 transaction, got %d", count)
}

count = len(slices.Collect(block.TransactionsFrom(1)))
if count != 0 {
t.Errorf("Expected to iterate over 0 transactions, got %d", count)
}
})
}

func reverseBytes(data []byte) []byte {
Expand Down
Loading
Loading