2026-02-20 23:29:42 +00:00
|
|
|
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
|
|
|
|
// You may obtain a copy of the licence at:
|
|
|
|
|
//
|
|
|
|
|
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package wallet
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
2026-03-22 01:49:26 +00:00
|
|
|
"dappco.re/go/core/blockchain/chain"
|
|
|
|
|
"dappco.re/go/core/blockchain/crypto"
|
|
|
|
|
"dappco.re/go/core/blockchain/types"
|
|
|
|
|
"dappco.re/go/core/blockchain/wire"
|
2026-04-04 20:42:56 +00:00
|
|
|
store "dappco.re/go/core/store"
|
2026-02-20 23:29:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func makeTestBlock(t *testing.T, height uint64, prevHash types.Hash,
|
|
|
|
|
destAccount *Account) (*types.Block, types.Hash) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
txPub, txSec, _ := crypto.GenerateKeys()
|
|
|
|
|
derivation, _ := crypto.GenerateKeyDerivation(
|
|
|
|
|
[32]byte(destAccount.ViewPublicKey), txSec)
|
|
|
|
|
ephPub, _ := crypto.DerivePublicKey(
|
|
|
|
|
derivation, 0, [32]byte(destAccount.SpendPublicKey))
|
|
|
|
|
|
|
|
|
|
minerTx := types.Transaction{
|
|
|
|
|
Version: 0,
|
|
|
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
|
|
|
|
|
Vout: []types.TxOutput{
|
|
|
|
|
types.TxOutputBare{
|
|
|
|
|
Amount: 1_000_000_000_000, // 1 LTHN
|
|
|
|
|
Target: types.TxOutToKey{Key: types.PublicKey(ephPub)},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Extra: BuildTxExtra(types.PublicKey(txPub)),
|
|
|
|
|
Attachment: wire.EncodeVarint(0),
|
|
|
|
|
}
|
|
|
|
|
minerTx.Signatures = [][]types.Signature{{}}
|
|
|
|
|
|
|
|
|
|
blk := &types.Block{
|
|
|
|
|
BlockHeader: types.BlockHeader{
|
|
|
|
|
MajorVersion: 1,
|
|
|
|
|
Timestamp: 1770897600 + height*120,
|
|
|
|
|
PrevID: prevHash,
|
|
|
|
|
},
|
|
|
|
|
MinerTx: minerTx,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash := wire.BlockHash(blk)
|
|
|
|
|
return blk, hash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWalletSyncAndBalance(t *testing.T) {
|
|
|
|
|
s, err := store.New(":memory:")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
|
|
acc, err := GenerateAccount()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
c := chain.New(s)
|
|
|
|
|
|
|
|
|
|
// Store 3 blocks, each paying 1 LTHN to our account.
|
|
|
|
|
var prevHash types.Hash
|
|
|
|
|
for h := uint64(0); h < 3; h++ {
|
|
|
|
|
blk, hash := makeTestBlock(t, h, prevHash, acc)
|
|
|
|
|
meta := &chain.BlockMeta{Hash: hash, Height: h}
|
|
|
|
|
if err := c.PutBlock(blk, meta); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
// Index the output.
|
|
|
|
|
txHash := wire.TransactionHash(&blk.MinerTx)
|
|
|
|
|
c.PutOutput(1_000_000_000_000, txHash, 0)
|
|
|
|
|
prevHash = hash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w := NewWallet(acc, s, c, nil)
|
|
|
|
|
if err := w.Sync(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
confirmed, locked, err := w.Balance()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All 3 blocks are coinbase with MinedMoneyUnlockWindow=10.
|
|
|
|
|
// Chain height = 3, so all 3 are locked (height + 10 > 3).
|
|
|
|
|
if locked != 3_000_000_000_000 {
|
|
|
|
|
t.Fatalf("locked = %d, want 3_000_000_000_000", locked)
|
|
|
|
|
}
|
|
|
|
|
if confirmed != 0 {
|
|
|
|
|
t.Fatalf("confirmed = %d, want 0 (all locked)", confirmed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWalletTransfers(t *testing.T) {
|
|
|
|
|
s, err := store.New(":memory:")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
|
|
acc, err := GenerateAccount()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
c := chain.New(s)
|
|
|
|
|
|
|
|
|
|
blk, hash := makeTestBlock(t, 0, types.Hash{}, acc)
|
|
|
|
|
meta := &chain.BlockMeta{Hash: hash, Height: 0}
|
|
|
|
|
c.PutBlock(blk, meta)
|
|
|
|
|
txHash := wire.TransactionHash(&blk.MinerTx)
|
|
|
|
|
c.PutOutput(1_000_000_000_000, txHash, 0)
|
|
|
|
|
|
|
|
|
|
w := NewWallet(acc, s, c, nil)
|
|
|
|
|
w.Sync()
|
|
|
|
|
|
|
|
|
|
transfers, err := w.Transfers()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(transfers) != 1 {
|
|
|
|
|
t.Fatalf("got %d transfers, want 1", len(transfers))
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-04 20:42:56 +00:00
|
|
|
|
|
|
|
|
func TestWalletScanTxMarksHTLCSpend(t *testing.T) {
|
|
|
|
|
s, err := store.New(":memory:")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
|
|
acc, err := GenerateAccount()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ki := types.KeyImage{0x42}
|
|
|
|
|
if err := putTransfer(s, &Transfer{
|
|
|
|
|
KeyImage: ki,
|
|
|
|
|
Amount: 100,
|
|
|
|
|
BlockHeight: 1,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w := &Wallet{
|
|
|
|
|
store: s,
|
|
|
|
|
scanner: NewV1Scanner(acc),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx := &types.Transaction{
|
|
|
|
|
Version: types.VersionPreHF4,
|
|
|
|
|
Vin: []types.TxInput{
|
|
|
|
|
types.TxInputHTLC{
|
|
|
|
|
Amount: 100,
|
|
|
|
|
KeyImage: ki,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := w.scanTx(tx, 10); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got, err := getTransfer(s, ki)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if !got.Spent {
|
|
|
|
|
t.Fatal("expected HTLC spend to be marked spent")
|
|
|
|
|
}
|
|
|
|
|
if got.SpentHeight != 10 {
|
|
|
|
|
t.Fatalf("spent height = %d, want 10", got.SpentHeight)
|
|
|
|
|
}
|
|
|
|
|
}
|