Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d7b018d
[WIP] Remove sync.Pool usage
oskarszoon Nov 26, 2025
31451e7
[WIP] Lua optimizations
oskarszoon Nov 26, 2025
da55ca2
[WIP] Up the Lua version
oskarszoon Nov 26, 2025
2c795c7
Optimized subtreeprocessor processing loop when emptying queue
icellan Nov 26, 2025
31cf7f8
Optimized emptying queue into subtreeprocessor
icellan Nov 27, 2025
f9e094c
Changed dequeue in subtreeprocessor to use AddNode without lock
icellan Nov 28, 2025
36a8031
[WIP] Add more debugging
oskarszoon Nov 28, 2025
8d4ddf1
Added subtreebench cli to profile queue -> subtree processing
icellan Nov 28, 2025
8eb5671
Added AddTxBatchColumnar
icellan Nov 28, 2025
77fb613
Updated subtreebench to latest subtreeprocessor method signature for …
icellan Nov 28, 2025
5b1e65c
Changed queueing in subtreeprocessor to work with batches instead if …
icellan Nov 28, 2025
f3b0633
Fixed counters in block assembly
icellan Nov 28, 2025
cac6153
Fixed counters, split maps up in subtreeprocessor
icellan Nov 28, 2025
6d2ad61
Fixed creating a new tx map after finding a block
icellan Nov 28, 2025
77bc3c5
Migrated k8sresolver from Endpoints to EndpointSlices API
ordishs Dec 1, 2025
a39faa7
Merge branch 'release/wip-scale-2' of github.com:bsv-blockchain/teran…
ordishs Dec 1, 2025
903f46c
feat(blockassembly): add comprehensive metrics for unmined transactio…
ordishs Dec 1, 2025
18752ff
Updated map implementation of currentTxMap
icellan Dec 1, 2025
b9b887a
Fixed test
icellan Dec 1, 2025
62487a7
changed saving of subtrees to use workers
icellan Dec 1, 2025
5c8916d
fix(tracing): ensure logging and metrics work when tracing disabled
ordishs Dec 1, 2025
4de3407
Merge branch 'release/wip-scale-2' of github.com:bsv-blockchain/teran…
ordishs Dec 1, 2025
c2d7ec0
Changed TxInpoints to be passed around as reference
icellan Dec 2, 2025
1e509f2
Added sync.Pool to SetTxMined actions
icellan Dec 2, 2025
a57d32a
feat(blockassembly): return empty block template during block processing
ordishs Dec 2, 2025
4f51a19
refactor(blockassembly): simplify GetMiningCandidate caching logic
ordishs Dec 2, 2025
e562c66
feat(blockassembly): add state transition and duration metrics
ordishs Dec 2, 2025
7c1b7a4
feat(monitoring): add Grafana dashboard for BlockAssembler state trac…
ordishs Dec 2, 2025
64c6843
fix(agents): add required frontmatter to bitcoin-expert agent
ordishs Dec 2, 2025
41933d5
fix(blockassembly): revert caching simplification and fix test regres…
ordishs Dec 2, 2025
1ce61a4
Merge branch 'main' of github.com:bsv-blockchain/teranode into releas…
oskarszoon Dec 3, 2025
08e2dc9
Fix lint
oskarszoon Dec 3, 2025
5215d94
Fixed race condition in currentItemsPerFile, added Aerospike setMined…
icellan Dec 3, 2025
d233cee
Fixed setting DAH on external blobs when DAH changes in setMinedMulti…
icellan Dec 3, 2025
7650fb1
Separated LUA script into multiple modules in Aerospike
icellan Dec 3, 2025
b5ba4f9
Added workers to loadUnmined and changed types to be reused
icellan Dec 3, 2025
3022778
Read loadUnmined from aerospike in workers and process in parallel
icellan Dec 3, 2025
eaf1145
Check for inflight blocks when setTxMined is called
icellan Dec 3, 2025
01114f9
Refined marking block as in flight processing of setTxMined
icellan Dec 3, 2025
8010f49
Changed currentTxMap to use swissMap, optimized moving forward multip…
icellan Dec 4, 2025
aadbe9d
Optimized moveForward empty block, added counter for dequeueing
icellan Dec 4, 2025
c010c58
Revert "Changed currentTxMap to use swissMap, optimized moving forwar…
icellan Dec 4, 2025
5dde2eb
Reverted swiss map changes
icellan Dec 4, 2025
f43d263
Changed GetBlockAssemblyState to always return, even if subtreehashes…
icellan Dec 4, 2025
0abc913
Fixed reorg with only moveForward blocks
icellan Dec 4, 2025
bbccf25
Fixed checking cap size of current subtree when processing queue
icellan Dec 4, 2025
860bddd
Use the subtree Size() function to determine next subtree size
icellan Dec 4, 2025
4142391
Increased record queue size to 10M in loadUnmined in Aerospike
icellan Dec 4, 2025
1b2b97d
Fixed bug in determining next subtree size
icellan Dec 4, 2025
76bf6e8
Added loadunmined benchmark, optimized loading from aerospike
icellan Dec 4, 2025
3a9e961
Changed wait time for mined in subtree processor to be max 300 seconds
icellan Dec 4, 2025
0742326
Fixed failing test
icellan Dec 5, 2025
262f5b8
Optimized loadUnmined with aerospike paritition splitting
icellan Dec 5, 2025
899e7a4
perf(utxo): optimize Aerospike Lua UDFs for high-throughput operations
ordishs Dec 3, 2025
57380ea
feat(utxo): add hash prefix support and UTXO headers export
ordishs Dec 5, 2025
ffb9a9a
fix(settings): preserve JSON marshaler types and array order in sorte…
ordishs Dec 5, 2025
2750a65
Added optional disk based loadUnmined that uses a lot less RAM
icellan Dec 5, 2025
ed4e962
Added option to set currentTxMap in parallel in moveForwardBlock
icellan Dec 5, 2025
b1bf4ab
Increased concurrency of block persister
icellan Dec 5, 2025
b9911a3
Made sure we retain the needed bloomfilters cached when validating bl…
icellan Dec 5, 2025
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
4 changes: 4 additions & 0 deletions .claude/agents/bitcoin-expert.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
name: bitcoin-expert
---

# bitcoin-expert

You are a Bitcoin expert with deep technical knowledge of the original Bitcoin protocol as defined in Satoshi Nakamoto's whitepaper and implemented in Bitcoin SV (BSV). Your expertise encompasses cryptography, distributed systems, and the economic incentives that make Bitcoin work.
Expand Down
15 changes: 14 additions & 1 deletion cmd/filereader/file_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"io"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/bsv-blockchain/go-bt/v2"
Expand All @@ -37,6 +38,7 @@ import (
"github.com/bsv-blockchain/teranode/services/utxopersister"
"github.com/bsv-blockchain/teranode/settings"
"github.com/bsv-blockchain/teranode/stores/blob"
"github.com/bsv-blockchain/teranode/stores/blob/options"
"github.com/bsv-blockchain/teranode/ulogger"
)

Expand Down Expand Up @@ -654,7 +656,18 @@ func getBlockStore(logger ulogger.Logger, settings *settings.Settings) blob.Stor
panic("blockstore config not found")
}

blockStore, err := blob.NewStore(logger, blockStoreURL)
var err error

hashPrefix := -2

if blockStoreURL.Query().Get("hashPrefix") != "" {
hashPrefix, err = strconv.Atoi(blockStoreURL.Query().Get("hashPrefix"))
if err != nil {
panic(err)
}
}

blockStore, err := blob.NewStore(logger, blockStoreURL, options.WithHashPrefix(hashPrefix))
if err != nil {
panic(err)
}
Expand Down
45 changes: 31 additions & 14 deletions cmd/seeder/seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
Expand All @@ -42,6 +43,7 @@ import (
"github.com/bsv-blockchain/teranode/services/utxopersister"
"github.com/bsv-blockchain/teranode/settings"
"github.com/bsv-blockchain/teranode/stores/blob"
"github.com/bsv-blockchain/teranode/stores/blob/options"
bloboptions "github.com/bsv-blockchain/teranode/stores/blob/options"
"github.com/bsv-blockchain/teranode/stores/blockchain"
blockchainoptions "github.com/bsv-blockchain/teranode/stores/blockchain/options"
Expand Down Expand Up @@ -85,7 +87,7 @@ func usage(msg string) {
//
//nolint:gocognit // Requires refactoring to reduce cognitive complexity
func Seeder(logger ulogger.Logger, appSettings *settings.Settings, inputDir string, hash string,
skipHeaders bool, skipUTXOs bool) {
skipHeaders bool, skipUTXOs bool, skipCheck bool) {
profilerAddr := appSettings.ProfilerAddr
if profilerAddr != "" {
go func() {
Expand Down Expand Up @@ -159,6 +161,7 @@ func Seeder(logger ulogger.Logger, appSettings *settings.Settings, inputDir stri
defer wg.Done()

logger.Infof("Processing headers...")
logger.Infof("Blockchain store: %s", appSettings.BlockChain.StoreURL)

// Process the headers
if err := processHeaders(ctx, logger, appSettings, headerFile); err != nil {
Expand All @@ -177,9 +180,10 @@ func Seeder(logger ulogger.Logger, appSettings *settings.Settings, inputDir stri
defer wg.Done()

logger.Infof("Processing UTXOs...")
logger.Infof("UTXO store: %s", appSettings.UtxoStore.UtxoStore.String())

// Process the UTXOs
if err := processUTXOs(ctx, logger, appSettings, utxoFile); err != nil {
if err := processUTXOs(ctx, logger, appSettings, utxoFile, skipCheck); err != nil {
logger.Errorf("Failed to process UTXOs: %v", err)
return
}
Expand Down Expand Up @@ -316,29 +320,42 @@ func processHeaders(ctx context.Context, logger ulogger.Logger, appSettings *set
// processUTXOs reads the UTXO set from a file and stores it in the UTXO store.
//
//nolint:gocognit // Requires refactoring to reduce cognitive complexity
func processUTXOs(ctx context.Context, logger ulogger.Logger, appSettings *settings.Settings, utxoFile string) error {
func processUTXOs(ctx context.Context, logger ulogger.Logger, appSettings *settings.Settings, utxoFile string, skipCheck bool) error {
blockStoreURL := appSettings.Block.BlockStore
if blockStoreURL == nil {
return errors.NewConfigurationError("blockstore URL not found in config")
}

logger.Infof("Using blockStore at %s", blockStoreURL)
var err error

blockStore, err := blob.NewStore(logger, blockStoreURL)
if err != nil {
return errors.NewStorageError("failed to create blockStore", err)
hashPrefix := -2

if blockStoreURL.Query().Get("hashPrefix") != "" {
hashPrefix, err = strconv.Atoi(blockStoreURL.Query().Get("hashPrefix"))
if err != nil {
panic(err)
}
}

var exists bool
logger.Infof("Using blockStore at %s with hashPrefix %d", blockStoreURL, hashPrefix)

exists, err = blockStore.Exists(ctx, nil, fileformat.FileTypeDat, bloboptions.WithFilename("lastProcessed"))
blockStore, err := blob.NewStore(logger, blockStoreURL, options.WithHashPrefix(hashPrefix))
if err != nil {
return errors.NewStorageError("failed to check if lastProcessed.dat exists", err)
return errors.NewStorageError("failed to create blockStore", err)
}

if exists {
logger.Errorf("lastProcessed.dat exists, skipping UTXOs")
return nil
if !skipCheck {
var exists bool

exists, err = blockStore.Exists(ctx, nil, fileformat.FileTypeDat, bloboptions.WithFilename("lastProcessed"), bloboptions.WithNoHashPrefix())
if err != nil {
return errors.NewStorageError("failed to check if lastProcessed.dat exists", err)
}

if exists {
logger.Errorf("lastProcessed.dat exists, skipping UTXOs")
return nil
}
}

logger.Infof("Using utxostore at %s", appSettings.UtxoStore.UtxoStore)
Expand Down Expand Up @@ -470,7 +487,7 @@ func processUTXOs(ctx context.Context, logger ulogger.Logger, appSettings *setti

heightStr := fmt.Sprintf("%d\n", height)

if err = blockStore.Set(ctx, nil, fileformat.FileTypeDat, []byte(heightStr), bloboptions.WithFilename("lastProcessed")); err != nil {
if err = blockStore.Set(ctx, nil, fileformat.FileTypeDat, []byte(heightStr), bloboptions.WithFilename("lastProcessed"), bloboptions.WithNoHashPrefix()); err != nil {
return errors.NewStorageError("failed to write height of %d to lastProcessed.dat", height, err)
}

Expand Down
130 changes: 129 additions & 1 deletion cmd/settings/Settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,140 @@ package settings

import (
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/bsv-blockchain/teranode/settings"
"github.com/bsv-blockchain/teranode/ulogger"
"github.com/ordishs/gocore"
)

// marshalSortedJSON marshals a struct to JSON with sorted keys at all levels.
// It recursively sorts struct fields alphabetically but leaves arrays/slices unchanged.
func marshalSortedJSON(v interface{}, indent string) ([]byte, error) {
sorted := sortValue(v)
return json.MarshalIndent(sorted, "", indent)
}

// sortValue recursively sorts struct fields but leaves slices/arrays unchanged
func sortValue(v interface{}) interface{} {
if v == nil {
return nil
}

val := reflect.ValueOf(v)
typ := reflect.TypeOf(v)

// Check if the type implements json.Marshaler - if so, preserve it as-is
// to maintain custom JSON marshaling behavior (like hash types that output as hex strings)
if _, ok := v.(json.Marshaler); ok {
return v
}

// Also check if pointer to this type implements json.Marshaler
if val.CanAddr() {
if _, ok := val.Addr().Interface().(json.Marshaler); ok {
return v
}
}

// Additional check: if the pointer type implements json.Marshaler, create a pointer
// This matches Go's JSON marshaling behavior which automatically takes addresses when needed
ptrType := reflect.PtrTo(typ)
if ptrType.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
// Create a new pointer to this value to enable custom marshaling
newVal := reflect.New(typ)
newVal.Elem().Set(val)
return newVal.Interface()
}

// Check if this type has a method set that implements json.Marshaler
// This handles cases where the interface check above might fail
if typ.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) {
return v
}

// Handle pointers
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return nil
}
return sortValue(val.Elem().Interface())
}

// Handle arrays - preserve byte arrays as-is for potential hex encoding
if val.Kind() == reflect.Array {
// Special case: byte arrays (like [32]byte hashes) should be preserved as-is
if val.Type().Elem().Kind() == reflect.Uint8 {
return v
}

// For other arrays, recursively process elements but don't sort the array itself
result := make([]interface{}, val.Len())
for i := 0; i < val.Len(); i++ {
result[i] = sortValue(val.Index(i).Interface())
}
return result
}

// Handle slices - leave them unchanged, only process nested structs
if val.Kind() == reflect.Slice {
if val.Len() == 0 {
return v
}

// Special case: convert byte slices to hex strings instead of base64
if val.Type().Elem().Kind() == reflect.Uint8 {
bytes := make([]byte, val.Len())
for i := 0; i < val.Len(); i++ {
bytes[i] = uint8(val.Index(i).Uint())
}
return fmt.Sprintf("%x", bytes)
}

// For other slices, recursively process elements but don't sort the slice itself
result := make([]interface{}, val.Len())
for i := 0; i < val.Len(); i++ {
result[i] = sortValue(val.Index(i).Interface())
}
return result
}

// Handle structs
if val.Kind() == reflect.Struct {
result := make(map[string]interface{})

for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)

// Skip unexported fields
if !fieldVal.CanInterface() {
continue
}

// Get JSON tag name, fallback to field name
jsonTag := field.Tag.Get("json")
fieldName := field.Name
if jsonTag != "" && jsonTag != "-" {
// Handle json:",omitempty" and similar
if idx := strings.Index(jsonTag, ","); idx != -1 {
fieldName = jsonTag[:idx]
} else {
fieldName = jsonTag
}
}

result[fieldName] = sortValue(fieldVal.Interface())
}
return result
}

// For all other types, return as-is
return v
}

// PrintSettings prints the application settings, version, and commit information in a structured format.
//
// This function is used to display the current application configuration and runtime details
Expand All @@ -41,7 +169,7 @@ func PrintSettings(logger ulogger.Logger, settings *settings.Settings, version,
stats := gocore.Config().Stats()
logger.Infof("STATS\n%s\nVERSION\n-------\n%s (%s)\n\n", stats, version, commit)

settingsJSON, err := json.MarshalIndent(settings, "", " ")
settingsJSON, err := marshalSortedJSON(settings, " ")
if err != nil {
logger.Errorf("Failed to marshal settings: %v", err)
} else {
Expand Down
Loading
Loading