chore: Go 1.26 modernization #2

Merged
Charon merged 5 commits from chore/go-1.26-modernization into main 2026-02-24 18:01:44 +00:00
21 changed files with 65 additions and 79 deletions

View file

@ -8,10 +8,11 @@
package chain
import (
"errors"
"fmt"
store "forge.lthn.ai/core/go-store"
"forge.lthn.ai/core/go-blockchain/types"
store "forge.lthn.ai/core/go-store"
)
// Chain manages blockchain storage and indexing.
@ -41,7 +42,7 @@ func (c *Chain) TopBlock() (*types.Block, *BlockMeta, error) {
return nil, nil, err
}
if h == 0 {
return nil, nil, fmt.Errorf("chain: no blocks stored")
return nil, nil, errors.New("chain: no blocks stored")
}
return c.GetBlockByHeight(h - 1)
}

View file

@ -28,10 +28,7 @@ func (c *Chain) NextDifficulty(height uint64, forks []config.HardFork) (uint64,
// LWMA needs N+1 entries (N solve-time intervals).
// Start from height 1 — genesis is excluded from the difficulty window.
maxLookback := difficulty.LWMAWindow + 1
lookback := height // height excludes genesis since we start from 1
if lookback > maxLookback {
lookback = maxLookback
}
lookback := min(height, maxLookback) // height excludes genesis since we start from 1
// Start from max(1, height - lookback) to exclude genesis.
startHeight := height - lookback
@ -48,7 +45,7 @@ func (c *Chain) NextDifficulty(height uint64, forks []config.HardFork) (uint64,
timestamps := make([]uint64, count)
cumulDiffs := make([]*big.Int, count)
for i := 0; i < count; i++ {
for i := range count {
meta, err := c.getBlockMeta(startHeight + uint64(i))
if err != nil {
// Fewer blocks than expected — use what we have.

View file

@ -10,6 +10,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
"regexp"
@ -66,10 +67,7 @@ func (c *Chain) Sync(ctx context.Context, client *rpc.Client, opts SyncOptions)
}
remaining := remoteHeight - localHeight
batch := uint64(syncBatchSize)
if remaining < batch {
batch = remaining
}
batch := min(remaining, uint64(syncBatchSize))
blocks, err := client.GetBlocksDetails(localHeight, batch)
if err != nil {
@ -413,7 +411,7 @@ var aggregatedRE = regexp.MustCompile(`"AGGREGATED"\s*:\s*\{([^}]+)\}`)
func parseBlockHeader(objectInJSON string) (*types.BlockHeader, error) {
m := aggregatedRE.FindStringSubmatch(objectInJSON)
if m == nil {
return nil, fmt.Errorf("AGGREGATED section not found in object_in_json")
return nil, errors.New("AGGREGATED section not found in object_in_json")
}
var hj blockHeaderJSON

View file

@ -7,6 +7,7 @@ package chain
import (
"bytes"
"errors"
"fmt"
"forge.lthn.ai/core/go-blockchain/config"
@ -31,7 +32,7 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error {
// Genesis block: prev_id must be zero.
if expectedHeight == 0 {
if !b.PrevID.IsZero() {
return fmt.Errorf("validate: genesis block has non-zero prev_id")
return errors.New("validate: genesis block has non-zero prev_id")
}
return nil
}

View file

@ -7,7 +7,7 @@ package consensus
import (
"fmt"
"sort"
"slices"
"forge.lthn.ai/core/go-blockchain/config"
"forge.lthn.ai/core/go-blockchain/types"
@ -50,7 +50,7 @@ func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, rec
func medianTimestamp(timestamps []uint64) uint64 {
sorted := make([]uint64, len(timestamps))
copy(sorted, timestamps)
sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
slices.Sort(sorted)
n := len(sorted)
if n == 0 {

View file

@ -27,7 +27,7 @@ func CheckDifficulty(hash types.Hash, difficulty uint64) bool {
// Convert hash to big.Int (little-endian as per CryptoNote convention).
// Reverse to big-endian for big.Int.
var be [32]byte
for i := 0; i < 32; i++ {
for i := range 32 {
be[i] = hash[31-i]
}
hashInt := new(big.Int).SetBytes(be[:])

View file

@ -6,6 +6,7 @@
package consensus
import (
"errors"
"fmt"
"math/bits"
@ -55,7 +56,7 @@ func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) {
// Since hi1 should be 0 for reasonable block sizes, simplify:
if hi1 > 0 {
return 0, fmt.Errorf("consensus: reward overflow")
return 0, errors.New("consensus: reward overflow")
}
hi2, lo2 := bits.Mul64(baseReward, lo1)

View file

@ -6,6 +6,7 @@
package consensus
import (
"errors"
"fmt"
"forge.lthn.ai/core/go-blockchain/config"
@ -222,7 +223,7 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn)
// Verify BPP range proof if present.
if len(proofs.bppProofBytes) > 0 && len(proofs.bppCommitments) > 0 {
if !crypto.VerifyBPP(proofs.bppProofBytes, proofs.bppCommitments) {
return fmt.Errorf("consensus: BPP range proof verification failed")
return errors.New("consensus: BPP range proof verification failed")
}
}

View file

@ -8,7 +8,7 @@ package crypto
import "C"
import (
"fmt"
"errors"
"unsafe"
)
@ -20,7 +20,7 @@ func PointMul8(pk [32]byte) ([32]byte, error) {
(*C.uint8_t)(unsafe.Pointer(&result[0])),
)
if rc != 0 {
return result, fmt.Errorf("crypto: point_mul8 failed")
return result, errors.New("crypto: point_mul8 failed")
}
return result, nil
}
@ -34,7 +34,7 @@ func PointDiv8(pk [32]byte) ([32]byte, error) {
(*C.uint8_t)(unsafe.Pointer(&result[0])),
)
if rc != 0 {
return result, fmt.Errorf("crypto: point_div8 failed")
return result, errors.New("crypto: point_div8 failed")
}
return result, nil
}
@ -48,7 +48,7 @@ func PointSub(a, b [32]byte) ([32]byte, error) {
(*C.uint8_t)(unsafe.Pointer(&result[0])),
)
if rc != 0 {
return result, fmt.Errorf("crypto: point_sub failed")
return result, errors.New("crypto: point_sub failed")
}
return result, nil
}
@ -81,7 +81,7 @@ func GenerateCLSAGGG(hash [32]byte, ring []byte, ringSize int,
(*C.uint8_t)(unsafe.Pointer(&sig[0])),
)
if rc != 0 {
return nil, fmt.Errorf("crypto: generate_CLSAG_GG failed")
return nil, errors.New("crypto: generate_CLSAG_GG failed")
}
return sig, nil
}

View file

@ -8,6 +8,7 @@ package crypto
import "C"
import (
"errors"
"fmt"
"unsafe"
)
@ -51,7 +52,7 @@ func GenerateKeyDerivation(pub [32]byte, sec [32]byte) ([32]byte, error) {
(*C.uint8_t)(unsafe.Pointer(&d[0])),
)
if rc != 0 {
return d, fmt.Errorf("crypto: generate_key_derivation failed")
return d, errors.New("crypto: generate_key_derivation failed")
}
return d, nil
}
@ -66,7 +67,7 @@ func DerivePublicKey(derivation [32]byte, index uint64, base [32]byte) ([32]byte
(*C.uint8_t)(unsafe.Pointer(&derived[0])),
)
if rc != 0 {
return derived, fmt.Errorf("crypto: derive_public_key failed")
return derived, errors.New("crypto: derive_public_key failed")
}
return derived, nil
}
@ -81,7 +82,7 @@ func DeriveSecretKey(derivation [32]byte, index uint64, base [32]byte) ([32]byte
(*C.uint8_t)(unsafe.Pointer(&derived[0])),
)
if rc != 0 {
return derived, fmt.Errorf("crypto: derive_secret_key failed")
return derived, errors.New("crypto: derive_secret_key failed")
}
return derived, nil
}

View file

@ -8,7 +8,7 @@ package crypto
import "C"
import (
"fmt"
"errors"
"unsafe"
)
@ -22,7 +22,7 @@ func GenerateKeyImage(pub [32]byte, sec [32]byte) ([32]byte, error) {
(*C.uint8_t)(unsafe.Pointer(&ki[0])),
)
if rc != 0 {
return ki, fmt.Errorf("crypto: generate_key_image failed")
return ki, errors.New("crypto: generate_key_image failed")
}
return ki, nil
}

View file

@ -8,7 +8,7 @@ package crypto
import "C"
import (
"fmt"
"errors"
"unsafe"
)
@ -22,7 +22,7 @@ func GenerateSignature(hash [32]byte, pub [32]byte, sec [32]byte) ([64]byte, err
(*C.uint8_t)(unsafe.Pointer(&sig[0])),
)
if rc != 0 {
return sig, fmt.Errorf("crypto: generate_signature failed")
return sig, errors.New("crypto: generate_signature failed")
}
return sig, nil
}
@ -60,7 +60,7 @@ func GenerateRingSignature(hash [32]byte, image [32]byte, pubs [][32]byte,
(*C.uint8_t)(unsafe.Pointer(&flatSigs[0])),
)
if rc != 0 {
return nil, fmt.Errorf("crypto: generate_ring_signature failed")
return nil, errors.New("crypto: generate_ring_signature failed")
}
sigs := make([][64]byte, n)

View file

@ -79,7 +79,7 @@ type PeerlistEntry struct {
func DecodePeerlist(blob []byte) []PeerlistEntry {
n := len(blob) / PeerlistEntrySize
entries := make([]PeerlistEntry, n)
for i := 0; i < n; i++ {
for i := range n {
off := i * PeerlistEntrySize
entries[i] = PeerlistEntry{
IP: binary.LittleEndian.Uint32(blob[off : off+4]),

View file

@ -252,7 +252,7 @@ func (r *ResponseChainEntry) Decode(data []byte) error {
func splitHashes(blob []byte, size int) [][]byte {
n := len(blob) / size
out := make([][]byte, n)
for i := 0; i < n; i++ {
for i := range n {
out[i] = blob[i*size : (i+1)*size]
}
return out

View file

@ -224,22 +224,13 @@ func (m *ExplorerModel) viewBlockList() string {
b.WriteByte('\n')
// Visible window centred on cursor.
visibleRows := m.height - 2 // header + bottom margin
if visibleRows < 1 {
visibleRows = 1
}
visibleRows := max(m.height-2, 1) // header + bottom margin
start := m.cursor - visibleRows/2
if start < 0 {
start = 0
}
start := max(m.cursor-visibleRows/2, 0)
end := start + visibleRows
if end > len(m.rows) {
end = len(m.rows)
start = end - visibleRows
if start < 0 {
start = 0
}
start = max(end-visibleRows, 0)
}
for i := start; i < end; i++ {
@ -356,13 +347,10 @@ func (m *ExplorerModel) loadBlocks() {
}
// Show up to 1000 most recent blocks.
count := int(height)
if count > 1000 {
count = 1000
}
count := min(int(height), 1000)
rows := make([]blockRow, count)
for i := 0; i < count; i++ {
for i := range count {
h := height - 1 - uint64(i)
blk, meta, err := m.chain.GetBlockByHeight(h)
if err != nil {
@ -381,9 +369,5 @@ func (m *ExplorerModel) loadBlocks() {
// pageSize returns the number of rows to jump for page up/down.
func pageSize(height int) int {
n := height - 3
if n < 1 {
n = 1
}
return n
return max(height-3, 1)
}

View file

@ -170,7 +170,7 @@ func base58Encode(data []byte) string {
fullBlocks := len(data) / 8
lastBlockSize := len(data) % 8
for i := 0; i < fullBlocks; i++ {
for i := range fullBlocks {
block := data[i*8 : (i+1)*8]
encoded := encodeBlock(block, 11)
result = append(result, encoded...)
@ -227,7 +227,7 @@ func base58Decode(s string) ([]byte, error) {
var result []byte
for i := 0; i < fullBlocks; i++ {
for i := range fullBlocks {
blockStr := s[i*11 : (i+1)*11]
decoded, err := decodeBlock(blockStr, 8)
if err != nil {

View file

@ -15,6 +15,7 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
@ -54,8 +55,8 @@ type Account struct {
SpendSecretKey types.SecretKey `json:"spend_secret_key"`
ViewPublicKey types.PublicKey `json:"view_public_key"`
ViewSecretKey types.SecretKey `json:"view_secret_key"`
CreatedAt uint64 `json:"created_at"`
Flags uint8 `json:"flags"`
CreatedAt uint64 `json:"created_at"`
Flags uint8 `json:"flags"`
}
// GenerateAccount creates a new account with random spend keys and a
@ -162,7 +163,7 @@ func LoadAccount(s *store.Store, password string) (*Account, error) {
}
if len(blob) < saltLen+nonceLen+1 {
return nil, fmt.Errorf("wallet: account data too short")
return nil, errors.New("wallet: account data too short")
}
salt := blob[:saltLen]

View file

@ -11,8 +11,10 @@ package wallet
import (
"bytes"
"cmp"
"errors"
"fmt"
"sort"
"slices"
"forge.lthn.ai/core/go-blockchain/config"
"forge.lthn.ai/core/go-blockchain/crypto"
@ -157,20 +159,16 @@ func (b *V1Builder) buildInput(src *Transfer) (types.TxInputToKey, inputMeta, er
})
// Sort by global index (consensus rule).
sort.Slice(ring, func(a, b int) bool {
return ring[a].GlobalIndex < ring[b].GlobalIndex
slices.SortFunc(ring, func(a, b RingMember) int {
return cmp.Compare(a.GlobalIndex, b.GlobalIndex)
})
// Find real index after sorting.
realIdx := -1
for j, m := range ring {
if m.GlobalIndex == src.GlobalIndex {
realIdx = j
break
}
}
realIdx := slices.IndexFunc(ring, func(m RingMember) bool {
return m.GlobalIndex == src.GlobalIndex
})
if realIdx < 0 {
return types.TxInputToKey{}, inputMeta{}, fmt.Errorf("real output not found in ring")
return types.TxInputToKey{}, inputMeta{}, errors.New("real output not found in ring")
}
// Build key offsets and public key list.

View file

@ -2,6 +2,7 @@ package wallet
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"strings"
@ -43,12 +44,12 @@ func MnemonicDecode(phrase string) ([32]byte, error) {
expected := checksumIndex(words[:24])
if words[24] != words[expected] {
return key, fmt.Errorf("wallet: mnemonic checksum failed")
return key, errors.New("wallet: mnemonic checksum failed")
}
n := uint32(numWords)
for i := 0; i < 8; i++ {
for i := range 8 {
w1, ok1 := wordIndex[words[i*3]]
w2, ok2 := wordIndex[words[i*3+1]]
w3, ok3 := wordIndex[words[i*3+2]]

View file

@ -10,15 +10,17 @@
package wallet
import (
"cmp"
"errors"
"fmt"
"sort"
"slices"
"strconv"
store "forge.lthn.ai/core/go-store"
"forge.lthn.ai/core/go-blockchain/chain"
"forge.lthn.ai/core/go-blockchain/rpc"
"forge.lthn.ai/core/go-blockchain/types"
"forge.lthn.ai/core/go-blockchain/wire"
store "forge.lthn.ai/core/go-store"
)
const (
@ -165,7 +167,7 @@ func (w *Wallet) Balance() (confirmed, locked uint64, err error) {
// Send constructs and submits a transaction.
func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transaction, error) {
if w.builder == nil || w.client == nil {
return nil, fmt.Errorf("wallet: no RPC client configured")
return nil, errors.New("wallet: no RPC client configured")
}
chainHeight, err := w.chain.Height()
@ -192,8 +194,8 @@ func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transactio
spendable = append(spendable, tr)
}
}
sort.Slice(spendable, func(i, j int) bool {
return spendable[i].Amount > spendable[j].Amount
slices.SortFunc(spendable, func(a, b Transfer) int {
return cmp.Compare(b.Amount, a.Amount) // descending
})
var selected []Transfer

View file

@ -55,7 +55,7 @@ func (d *Decoder) ReadVarint() uint64 {
}
var val uint64
var shift uint
for i := 0; i < MaxVarintLen; i++ {
for range MaxVarintLen {
_, d.err = io.ReadFull(d.r, d.buf[:1])
if d.err != nil {
return 0