// 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 tui import ( "testing" "time" store "dappco.re/go/core/store" "dappco.re/go/core/blockchain/chain" "dappco.re/go/core/blockchain/types" "dappco.re/go/core/blockchain/wire" ) // testCoinbaseTx returns a minimal v1 coinbase transaction that round-trips // cleanly through the wire encoder/decoder. func testCoinbaseTx(height uint64) types.Transaction { return types.Transaction{ Version: 1, Vin: []types.TxInput{types.TxInputGenesis{Height: height}}, Vout: []types.TxOutput{types.TxOutputBare{ Amount: 1000000, Target: types.TxOutToKey{Key: types.PublicKey{0x01}}, }}, Extra: wire.EncodeVarint(0), Attachment: wire.EncodeVarint(0), } } // seedChain creates an in-memory chain with n blocks. func seedChain(t *testing.T, n int) *chain.Chain { t.Helper() s, err := store.New(":memory:") if err != nil { t.Fatalf("store.New: %v", err) } t.Cleanup(func() { s.Close() }) c := chain.New(s) for i := 0; i < n; i++ { blk := &types.Block{ BlockHeader: types.BlockHeader{ MajorVersion: 1, Timestamp: 1770897600 + uint64(i)*120, }, MinerTx: testCoinbaseTx(uint64(i)), } meta := &chain.BlockMeta{ Hash: types.Hash{byte(i)}, Height: uint64(i), Timestamp: 1770897600 + uint64(i)*120, Difficulty: 1000 + uint64(i)*100, } if err := c.PutBlock(blk, meta); err != nil { t.Fatalf("PutBlock(%d): %v", i, err) } } return c } func TestNewNode_Good(t *testing.T) { c := seedChain(t, 1) n := NewNode(c) if n == nil { t.Fatal("NewNode returned nil") } if n.Chain() != c { t.Error("Chain() does not return the same chain passed to NewNode") } } func TestNode_Status_Good(t *testing.T) { c := seedChain(t, 10) n := NewNode(c) status, err := n.Status() if err != nil { t.Fatalf("Status: %v", err) } if status.Height != 10 { t.Errorf("height: got %d, want 10", status.Height) } // Top block is at height 9, difficulty = 1000 + 9*100 = 1900. if status.Difficulty != 1900 { t.Errorf("difficulty: got %d, want 1900", status.Difficulty) } wantHash := types.Hash{9} if status.TopHash != wantHash { t.Errorf("top hash: got %x, want %x", status.TopHash, wantHash) } wantTime := time.Unix(int64(1770897600+9*120), 0) if !status.TipTime.Equal(wantTime) { t.Errorf("tip time: got %v, want %v", status.TipTime, wantTime) } } func TestNode_Status_Empty(t *testing.T) { c := seedChain(t, 0) n := NewNode(c) status, err := n.Status() if err != nil { t.Fatalf("Status: %v", err) } if status.Height != 0 { t.Errorf("height: got %d, want 0", status.Height) } } func TestNode_WaitForStatus_Good(t *testing.T) { c := seedChain(t, 3) n := NewNode(c) cmd := n.WaitForStatus() if cmd == nil { t.Fatal("WaitForStatus returned nil cmd") } msg := cmd() status, ok := msg.(NodeStatusMsg) if !ok { t.Fatalf("WaitForStatus cmd returned %T, want NodeStatusMsg", msg) } if status.Height != 3 { t.Errorf("height: got %d, want 3", status.Height) } } func TestNode_Tick_Good(t *testing.T) { c := seedChain(t, 5) n := NewNode(c) n.interval = 50 * time.Millisecond start := time.Now() msg := n.Tick()() elapsed := time.Since(start) status, ok := msg.(NodeStatusMsg) if !ok { t.Fatalf("Tick cmd returned %T, want NodeStatusMsg", msg) } if status.Height != 5 { t.Errorf("height: got %d, want 5", status.Height) } if elapsed < 40*time.Millisecond { t.Errorf("Tick returned too quickly (%v), expected at least ~50ms", elapsed) } }