go-blockchain/chain/difficulty_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

209 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 chain
import (
"testing"
"dappco.re/go/core/blockchain/config"
"dappco.re/go/core/blockchain/types"
store "dappco.re/go/core/store"
"github.com/stretchr/testify/require"
)
// preHF6Forks is a fork schedule where HF6 never activates,
// so both PoW and PoS targets stay at 120s.
var preHF6Forks = []config.HardFork{
{Version: config.HF0Initial, Height: 0},
}
// hf6ActiveForks is a fork schedule where HF6 activates at height 100,
// switching both PoW and PoS targets to 240s from block 101 onwards.
var hf6ActiveForks = []config.HardFork{
{Version: config.HF0Initial, Height: 0},
{Version: config.HF1, Height: 0},
{Version: config.HF2, Height: 0},
{Version: config.HF3, Height: 0},
{Version: config.HF4Zarcanum, Height: 0},
{Version: config.HF5, Height: 0},
{Version: config.HF6, Height: 100},
}
// storeBlocks inserts count blocks with constant intervals and difficulty.
func storeBlocks(t *testing.T, c *Chain, count int, interval uint64, baseDiff uint64) {
t.Helper()
for i := uint64(0); i < uint64(count); i++ {
err := c.PutBlock(&types.Block{}, &BlockMeta{
Hash: types.Hash{byte(i + 1)},
Height: i,
Timestamp: i * interval,
Difficulty: baseDiff,
CumulativeDiff: baseDiff * (i + 1),
})
require.NoError(t, err)
}
}
func TestNextDifficulty_Genesis(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
diff, err := c.NextDifficulty(0, preHF6Forks)
require.NoError(t, err)
require.Equal(t, uint64(1), diff)
}
func TestNextDifficulty_FewBlocks(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
// Store genesis + 4 blocks with constant 120s intervals and difficulty 1000.
// Genesis at height 0 is excluded from the LWMA window.
storeBlocks(t, c, 5, 120, 1000)
// Next difficulty for height 5 uses blocks 1-4 (n=3 intervals).
// LWMA formula with constant D and T gives D/n = 1000/3 = 333.
diff, err := c.NextDifficulty(5, preHF6Forks)
require.NoError(t, err)
require.Greater(t, diff, uint64(0))
expected := uint64(333)
require.Equal(t, expected, diff)
}
func TestNextDifficulty_EmptyChain(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
// Height 1 with no blocks stored -- should return starter difficulty.
diff, err := c.NextDifficulty(1, preHF6Forks)
require.NoError(t, err)
require.Equal(t, uint64(1), diff)
}
// --- HF6 boundary tests ---
func TestNextDifficulty_HF6Boundary_Good(t *testing.T) {
// Verify that blocks at height <= 100 use the 120s target and blocks
// at height > 100 use the 240s target, given hf6ActiveForks.
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
storeBlocks(t, c, 105, 120, 1000)
// Height 100 -- HF6 activates at heights > 100, so this is pre-HF6.
diffPre, err := c.NextDifficulty(100, hf6ActiveForks)
require.NoError(t, err)
// Height 101 -- HF6 is active (height > 100), target becomes 240s.
diffPost, err := c.NextDifficulty(101, hf6ActiveForks)
require.NoError(t, err)
// With 120s actual intervals and a 240s target, LWMA should produce
// lower difficulty than with a 120s target. The post-HF6 difficulty
// should differ from the pre-HF6 difficulty because the target doubled.
require.NotEqual(t, diffPre, diffPost,
"difficulty should change across HF6 boundary (120s vs 240s target)")
}
func TestNextDifficulty_HF6Boundary_Bad(t *testing.T) {
// HF6 at height 999,999,999 (mainnet default) -- should never activate
// for realistic heights, so the target stays at 120s.
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
storeBlocks(t, c, 105, 120, 1000)
forks := config.MainnetForks
diff100, err := c.NextDifficulty(100, forks)
require.NoError(t, err)
diff101, err := c.NextDifficulty(101, forks)
require.NoError(t, err)
// Both should use the same 120s target -- no HF6 in sight.
require.Equal(t, diff100, diff101,
"difficulty should be identical when HF6 is far in the future")
}
func TestNextDifficulty_HF6Boundary_Ugly(t *testing.T) {
// HF6 at height 0 (active from genesis) -- the 240s target should
// apply from the very first difficulty calculation.
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
storeBlocks(t, c, 5, 240, 1000)
genesisHF6 := []config.HardFork{
{Version: config.HF0Initial, Height: 0},
{Version: config.HF6, Height: 0},
}
diff, err := c.NextDifficulty(4, genesisHF6)
require.NoError(t, err)
require.Greater(t, diff, uint64(0))
}
// --- PoS difficulty tests ---
func TestNextPoSDifficulty_Good(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
storeBlocks(t, c, 5, 120, 1000)
// Pre-HF6: PoS target should be 120s (same as PoW).
diff, err := c.NextPoSDifficulty(5, preHF6Forks)
require.NoError(t, err)
require.Equal(t, uint64(333), diff)
}
func TestNextPoSDifficulty_HF6Boundary_Good(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
storeBlocks(t, c, 105, 120, 1000)
// Height 100 -- pre-HF6.
diffPre, err := c.NextPoSDifficulty(100, hf6ActiveForks)
require.NoError(t, err)
// Height 101 -- post-HF6, target becomes 240s.
diffPost, err := c.NextPoSDifficulty(101, hf6ActiveForks)
require.NoError(t, err)
require.NotEqual(t, diffPre, diffPost,
"PoS difficulty should change across HF6 boundary")
}
func TestNextPoSDifficulty_Genesis(t *testing.T) {
s, err := store.New(":memory:")
require.NoError(t, err)
defer s.Close()
c := New(s)
diff, err := c.NextPoSDifficulty(0, preHF6Forks)
require.NoError(t, err)
require.Equal(t, uint64(1), diff)
}