go-blockchain/wallet/transfer_test.go
Snider 34128d8e98
Some checks failed
Security Scan / security (pull_request) Successful in 11s
Test / Test (pull_request) Failing after 19s
refactor: migrate module path to dappco.re/go/core/blockchain
Update go.mod module line, all require/replace directives, and every
.go import path from forge.lthn.ai/core/go-blockchain to
dappco.re/go/core/blockchain. Add replace directives to bridge
dappco.re paths to existing forge.lthn.ai registry during migration.
Update CLAUDE.md, README, and docs to reflect the new module path.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 01:49:26 +00:00

272 lines
5.8 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 wallet
import (
"testing"
store "dappco.re/go/core/store"
"dappco.re/go/core/blockchain/types"
)
func newTestStore(t *testing.T) *store.Store {
t.Helper()
s, err := store.New(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { s.Close() })
return s
}
func TestTransferPutGet(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0x42
tr := Transfer{
TxHash: types.Hash{1},
OutputIndex: 0,
Amount: 1000,
BlockHeight: 10,
KeyImage: ki,
}
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
got, err := getTransfer(s, ki)
if err != nil {
t.Fatal(err)
}
if got.Amount != 1000 {
t.Fatalf("amount = %d, want 1000", got.Amount)
}
if got.TxHash != tr.TxHash {
t.Fatalf("tx hash mismatch: got %s", got.TxHash)
}
if got.KeyImage != ki {
t.Fatalf("key image mismatch: got %s", got.KeyImage)
}
}
func TestTransferGetNotFound(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0xFF
_, err := getTransfer(s, ki)
if err == nil {
t.Fatal("expected error for missing transfer")
}
}
func TestTransferOverwrite(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0x01
tr := Transfer{Amount: 500, BlockHeight: 5, KeyImage: ki}
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
tr.Amount = 999
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
got, err := getTransfer(s, ki)
if err != nil {
t.Fatal(err)
}
if got.Amount != 999 {
t.Fatalf("amount = %d, want 999 after overwrite", got.Amount)
}
}
func TestTransferMarkSpent(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0x43
tr := Transfer{Amount: 500, BlockHeight: 5, KeyImage: ki}
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
if err := markTransferSpent(s, ki, 20); err != nil {
t.Fatal(err)
}
got, err := getTransfer(s, ki)
if err != nil {
t.Fatal(err)
}
if !got.Spent {
t.Fatal("should be spent")
}
if got.SpentHeight != 20 {
t.Fatalf("spent height = %d, want 20", got.SpentHeight)
}
}
func TestTransferMarkSpentNotFound(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0xDE
if err := markTransferSpent(s, ki, 10); err == nil {
t.Fatal("expected error marking non-existent transfer as spent")
}
}
func TestTransferList(t *testing.T) {
s := newTestStore(t)
for i := byte(0); i < 3; i++ {
var ki types.KeyImage
ki[0] = i + 1
tr := Transfer{
Amount: uint64(i+1) * 100,
BlockHeight: uint64(i),
KeyImage: ki,
}
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
}
// Mark second transfer as spent.
var ki types.KeyImage
ki[0] = 2
if err := markTransferSpent(s, ki, 10); err != nil {
t.Fatal(err)
}
transfers, err := listTransfers(s)
if err != nil {
t.Fatal(err)
}
if len(transfers) != 3 {
t.Fatalf("got %d transfers, want 3", len(transfers))
}
unspent := 0
for _, tr := range transfers {
if !tr.Spent {
unspent++
}
}
if unspent != 2 {
t.Fatalf("got %d unspent, want 2", unspent)
}
}
func TestTransferListEmpty(t *testing.T) {
s := newTestStore(t)
transfers, err := listTransfers(s)
if err != nil {
t.Fatal(err)
}
if len(transfers) != 0 {
t.Fatalf("got %d transfers, want 0 for empty store", len(transfers))
}
}
func TestTransferSpendableBasic(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5}
if !tr.IsSpendable(20, false) {
t.Fatal("unspent transfer should be spendable")
}
}
func TestTransferSpendableSpent(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, Spent: true}
if tr.IsSpendable(20, false) {
t.Fatal("spent transfer should not be spendable")
}
}
func TestTransferSpendableCoinbaseImmature(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
// MinedMoneyUnlockWindow is 10, so block 5 + 10 = 15 > 10.
if tr.IsSpendable(10, false) {
t.Fatal("immature coinbase should not be spendable")
}
}
func TestTransferSpendableCoinbaseMature(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
// MinedMoneyUnlockWindow is 10, so block 5 + 10 = 15 <= 20.
if !tr.IsSpendable(20, false) {
t.Fatal("mature coinbase should be spendable")
}
}
func TestTransferSpendableCoinbaseBoundary(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
// Exact boundary: 5 + 10 = 15 == 15, not greater, so spendable.
if !tr.IsSpendable(15, false) {
t.Fatal("coinbase at exact maturity boundary should be spendable")
}
}
func TestTransferSpendableUnlockTime(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, UnlockTime: 50}
if tr.IsSpendable(30, false) {
t.Fatal("transfer with future unlock time should not be spendable")
}
if !tr.IsSpendable(50, false) {
t.Fatal("transfer at exact unlock height should be spendable")
}
if !tr.IsSpendable(100, false) {
t.Fatal("transfer past unlock height should be spendable")
}
}
func TestTransferSpendableUnlockTimeZero(t *testing.T) {
tr := Transfer{Amount: 1000, BlockHeight: 5, UnlockTime: 0}
if !tr.IsSpendable(1, false) {
t.Fatal("transfer with zero unlock time should be spendable")
}
}
func TestTransferKeyPairFields(t *testing.T) {
s := newTestStore(t)
var ki types.KeyImage
ki[0] = 0x99
var pub types.PublicKey
pub[0] = 0xAA
var sec types.SecretKey
sec[0] = 0xBB
tr := Transfer{
Amount: 2000,
BlockHeight: 50,
KeyImage: ki,
EphemeralKey: KeyPair{Public: pub, Secret: sec},
}
if err := putTransfer(s, &tr); err != nil {
t.Fatal(err)
}
got, err := getTransfer(s, ki)
if err != nil {
t.Fatal(err)
}
if got.EphemeralKey.Public != pub {
t.Fatal("ephemeral public key mismatch")
}
if got.EphemeralKey.Secret != sec {
t.Fatal("ephemeral secret key mismatch")
}
}