1717package downloader
1818
1919import (
20+ "encoding/json"
2021 "errors"
2122 "fmt"
2223 "math/big"
@@ -28,6 +29,7 @@ import (
2829
2930 ethereum "github.com/XinFinOrg/XDPoSChain"
3031 "github.com/XinFinOrg/XDPoSChain/common"
32+ engine_v2 "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
3133 "github.com/XinFinOrg/XDPoSChain/core/rawdb"
3234 "github.com/XinFinOrg/XDPoSChain/core/types"
3335 "github.com/XinFinOrg/XDPoSChain/ethdb"
@@ -62,6 +64,10 @@ type downloadTester struct {
6264
6365 insertHeaderChainHook func ([]* types.Header ) error
6466
67+ // configOverride, when non-nil, is returned by Config() instead of the
68+ // default TestChainConfig. Used by tests that require XDPoS to be active.
69+ configOverride * params.ChainConfig
70+
6571 lock sync.RWMutex
6672}
6773
@@ -344,6 +350,9 @@ func (dl *downloadTester) handleProposedBlock(header *types.Header) error {
344350
345351// Config retrieves the blockchain's chain configuration.
346352func (dl * downloadTester ) Config () * params.ChainConfig {
353+ if dl .configOverride != nil {
354+ return dl .configOverride
355+ }
347356 config := * params .TestChainConfig
348357 config .Eip1559Block = big .NewInt (0 )
349358 return & config
@@ -1803,3 +1812,236 @@ func testReorgProtectionDoesNotStallSync(t *testing.T, protocol int, mode SyncMo
18031812 })
18041813 }
18051814}
1815+
1816+ // TestSetPivotBlockStoresFields verifies that SetPivotBlock persists the pivot
1817+ // number, hash, and state root onto the downloader for later use during sync.
1818+ func TestSetPivotBlockStoresFields (t * testing.T ) {
1819+ t .Parallel ()
1820+
1821+ tester := newTester ()
1822+ // Provide an XDPoS config so SetPivotBlock does not short-circuit.
1823+ tester .configOverride = params .TestXDPoSMockChainConfig
1824+ defer tester .terminate ()
1825+ d := tester .downloader
1826+
1827+ wantNumber := uint64 (1000 )
1828+ wantHash := common .HexToHash ("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" )
1829+ wantRoot := common .HexToHash ("0xcafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" )
1830+
1831+ d .SetPivotBlock (wantNumber , wantHash , wantRoot )
1832+
1833+ if d .pivotNumber != wantNumber {
1834+ t .Errorf ("pivotNumber mismatch: have %v, want %v" , d .pivotNumber , wantNumber )
1835+ }
1836+ if d .pivotHash != wantHash {
1837+ t .Errorf ("pivotHash mismatch: have %v, want %v" , d .pivotHash , wantHash )
1838+ }
1839+ if d .pivotRoot != wantRoot {
1840+ t .Errorf ("pivotRoot mismatch: have %v, want %v" , d .pivotRoot , wantRoot )
1841+ }
1842+ }
1843+
1844+ // TestSetPivotBlockGapCalculation verifies the gap pivot number derivation
1845+ // produced by SetPivotBlock for a range of primary pivot numbers.
1846+ //
1847+ // With TestXDPoSMockChainConfig (Epoch=900, Gap=450):
1848+ //
1849+ // epochBase = pivot - pivot%900
1850+ // baseGap = epochBase-450 (or 900-450=450 when epochBase < 450)
1851+ // gaps = { baseGap + 900*i | i=0,1,… while value < pivot }
1852+ func TestSetPivotBlockGapCalculation (t * testing.T ) {
1853+ t .Parallel ()
1854+
1855+ tester := newTester ()
1856+ // TestXDPoSMockChainConfig has Epoch=900, Gap=450.
1857+ tester .configOverride = params .TestXDPoSMockChainConfig
1858+ defer tester .terminate ()
1859+ d := tester .downloader
1860+
1861+ tests := []struct {
1862+ pivot uint64
1863+ wantGaps []uint64
1864+ }{
1865+ // pivot ≤ baseGap(450): no gap numbers are strictly less than pivot
1866+ {pivot : 200 , wantGaps : nil },
1867+ {pivot : 450 , wantGaps : nil },
1868+ // first gap (450) is below pivot for the first time
1869+ {pivot : 451 , wantGaps : []uint64 {450 }},
1870+ // pivot at exact epoch boundary (900): only gap at 450
1871+ // epochBase=900, baseGap=900-450=450; 450<900→add, 1350≥900→stop
1872+ {pivot : 900 , wantGaps : []uint64 {450 }},
1873+ // pivot at baseGap+epoch (1350): 450<1350→add, 1350≥1350→stop
1874+ {pivot : 1350 , wantGaps : []uint64 {450 }},
1875+ // pivot just above 1350: both 450 and 1350 qualify
1876+ // epochBase=900, baseGap=450; 450<1351→add, 1350<1351→add, 2250≥1351→stop
1877+ {pivot : 1351 , wantGaps : []uint64 {450 , 1350 }},
1878+ // pivot at 2*epoch (1800): epochBase=1800, baseGap=1800-450=1350;
1879+ // 1350<1800→add, 2250≥1800→stop → only [1350]
1880+ {pivot : 1800 , wantGaps : []uint64 {1350 }},
1881+ // pivot just above 2250 to get two gaps in a different epoch window:
1882+ // epochBase=1800, baseGap=1350; 1350<2251→add, 2250<2251→add, 3150≥2251→stop
1883+ {pivot : 2251 , wantGaps : []uint64 {1350 , 2250 }},
1884+ }
1885+
1886+ for _ , tc := range tests {
1887+ d .SetPivotBlock (tc .pivot , common.Hash {}, common.Hash {})
1888+
1889+ d .pivotGapLock .RLock ()
1890+ got := make ([]uint64 , len (d .pivotGapNumbers ))
1891+ copy (got , d .pivotGapNumbers )
1892+ d .pivotGapLock .RUnlock ()
1893+
1894+ if len (got ) != len (tc .wantGaps ) {
1895+ t .Errorf ("pivot %d: gap count mismatch: have %v, want %v" , tc .pivot , got , tc .wantGaps )
1896+ continue
1897+ }
1898+ for i , g := range got {
1899+ if g != tc .wantGaps [i ] {
1900+ t .Errorf ("pivot %d: gap[%d] = %v, want %v" , tc .pivot , i , g , tc .wantGaps [i ])
1901+ }
1902+ }
1903+ }
1904+ }
1905+
1906+ // TestFastSyncPivotHashMismatch checks that processFastSyncContent returns a
1907+ // descriptive "pivot block hash mismatch" error when the configured pivot hash
1908+ // does not match the actual downloaded pivot block.
1909+ func TestFastSyncPivotHashMismatch (t * testing.T ) {
1910+ t .Parallel ()
1911+
1912+ tester := newTester ()
1913+ // XDPoS config is required so SetPivotBlock can compute gap numbers.
1914+ // TestXDPoSMockChainConfig has Epoch=900, Gap=450.
1915+ tester .configOverride = params .TestXDPoSMockChainConfig
1916+ defer tester .terminate ()
1917+
1918+ // Use a chain short enough to be fast but long enough to trigger state sync.
1919+ chainLen := 300
1920+ chain := testChainBase .shorten (chainLen )
1921+ tester .newPeer ("peer" , 63 , chain )
1922+
1923+ // Identify the natural pivot block so we can supply the correct state root
1924+ // (so state sync succeeds) while feeding a wrong hash (so the check fires).
1925+ // Natural pivot = headBlock().Number - fsMinFullBlocks = (chainLen-1) - fsMinFullBlocks.
1926+ pivotNum := uint64 (chainLen - 1 - fsMinFullBlocks ) // = 235
1927+ pivotRoot := chain.headerm [chain.chain [pivotNum ]].Root
1928+
1929+ wrongHash := common .HexToHash ("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" )
1930+ tester .downloader .SetPivotBlock (pivotNum , wrongHash , pivotRoot )
1931+
1932+ err := tester .sync ("peer" , nil , FastSync )
1933+ if err == nil {
1934+ t .Fatal ("expected pivot hash mismatch error, got nil" )
1935+ }
1936+ if ! strings .Contains (err .Error (), "pivot block hash mismatch" ) {
1937+ t .Fatalf ("unexpected error: %q (want substring %q)" , err .Error (), "pivot block hash mismatch" )
1938+ }
1939+ }
1940+
1941+ // TestFastSyncConfiguredPivotHashMatch verifies that setting the correct pivot
1942+ // hash and state root allows fast sync to complete successfully.
1943+ func TestFastSyncConfiguredPivotHashMatch (t * testing.T ) {
1944+ t .Parallel ()
1945+
1946+ tester := newTester ()
1947+ // XDPoS config is required so SetPivotBlock can compute gap numbers.
1948+ // TestXDPoSMockChainConfig has Epoch=900, Gap=450.
1949+ tester .configOverride = params .TestXDPoSMockChainConfig
1950+ defer tester .terminate ()
1951+
1952+ chainLen := 300
1953+ chain := testChainBase .shorten (chainLen )
1954+ tester .newPeer ("peer" , 63 , chain )
1955+
1956+ // Natural pivot = headBlock().Number - fsMinFullBlocks = (chainLen-1) - fsMinFullBlocks.
1957+ pivotNum := uint64 (chainLen - 1 - fsMinFullBlocks ) // = 235
1958+ pivotHash := chain.headerm [chain.chain [pivotNum ]].Hash ()
1959+ pivotRoot := chain.headerm [chain.chain [pivotNum ]].Root
1960+
1961+ // With Epoch=900 and pivot=235 the calculated baseGap=450 > pivot, so
1962+ // pivotGapNumbers will be empty – this test focuses purely on hash verification.
1963+ tester .downloader .SetPivotBlock (pivotNum , pivotHash , pivotRoot )
1964+
1965+ if err := tester .sync ("peer" , nil , FastSync ); err != nil {
1966+ t .Fatalf ("fast sync with correct pivot hash failed: %v" , err )
1967+ }
1968+ assertOwnChain (t , tester , chainLen )
1969+ }
1970+
1971+ // TestFastSyncGapPivotSync exercises the gap-pivot state-sync path: when the
1972+ // configured pivot is high enough that SetPivotBlock calculates one or more gap
1973+ // pivot numbers, processFastSyncContent must state-sync each gap block and
1974+ // generate a snapshot for it before committing the primary pivot.
1975+ //
1976+ // Chain layout (Epoch=900, Gap=450, pivot=536, gap pivot=[450]):
1977+ //
1978+ // blocks 1-535 → fast-sync (receipts)
1979+ // block 450 → gap pivot: state synced + snapshot generated
1980+ // block 536 → primary pivot: state synced + committed
1981+ // blocks 537-600 → full-sync
1982+ func TestFastSyncGapPivotSync (t * testing.T ) {
1983+ t .Parallel ()
1984+
1985+ tester := newTester ()
1986+ // XDPoS config is required so SetPivotBlock can compute gap numbers.
1987+ // TestXDPoSMockChainConfig has Epoch=900, Gap=450.
1988+ tester .configOverride = params .TestXDPoSMockChainConfig
1989+ defer tester .terminate ()
1990+
1991+ // 600 blocks: natural pivot = 600-64 = 536, gap pivot = 450.
1992+ chainLen := 600
1993+ chain := testChainBase .shorten (chainLen )
1994+ tester .newPeer ("peer" , 63 , chain )
1995+
1996+ // Natural pivot = headBlock().Number - fsMinFullBlocks = (chainLen-1) - fsMinFullBlocks.
1997+ pivotNum := uint64 (chainLen - 1 - fsMinFullBlocks ) // = 535
1998+ pivotHash := chain.headerm [chain.chain [pivotNum ]].Hash ()
1999+ pivotRoot := chain.headerm [chain.chain [pivotNum ]].Root
2000+
2001+ tester .downloader .SetPivotBlock (pivotNum , pivotHash , pivotRoot )
2002+
2003+ // After SetPivotBlock the gap list should contain exactly block 450:
2004+ // epochBase=0 (535<900), baseGap=450, first gap=450 < 535.
2005+ tester .downloader .pivotGapLock .RLock ()
2006+ gaps := make ([]uint64 , len (tester .downloader .pivotGapNumbers ))
2007+ copy (gaps , tester .downloader .pivotGapNumbers )
2008+ tester .downloader .pivotGapLock .RUnlock ()
2009+
2010+ if len (gaps ) != 1 || gaps [0 ] != 450 {
2011+ t .Fatalf ("expected gap pivots [450], got %v" , gaps )
2012+ }
2013+
2014+ if err := tester .sync ("peer" , nil , FastSync ); err != nil {
2015+ t .Fatalf ("fast sync with gap pivot failed: %v" , err )
2016+ }
2017+ assertOwnChain (t , tester , chainLen )
2018+
2019+ // After a successful sync the gap list should have been cleared.
2020+ tester .downloader .pivotGapLock .RLock ()
2021+ remaining := len (tester .downloader .pivotGapNumbers )
2022+ tester .downloader .pivotGapLock .RUnlock ()
2023+ if remaining != 0 {
2024+ t .Errorf ("pivotGapNumbers not cleared after sync: %d entries remain" , remaining )
2025+ }
2026+
2027+ // Verify that the snapshot for the gap pivot block (450) was stored and can
2028+ // be loaded back from the downloader's state database.
2029+ gapBlockHash := chain .headerm [chain .chain [450 ]].Hash ()
2030+ blob , err := rawdb .ReadXdposV2Snapshot (tester .downloader .stateDB , gapBlockHash )
2031+ if err != nil {
2032+ t .Fatalf ("snapshot for gap pivot block 450 not found in stateDB: %v" , err )
2033+ }
2034+ if len (blob ) == 0 {
2035+ t .Fatal ("snapshot blob for gap pivot block 450 is empty" )
2036+ }
2037+ var snap engine_v2.SnapshotV2
2038+ if err := json .Unmarshal (blob , & snap ); err != nil {
2039+ t .Fatalf ("failed to unmarshal gap pivot snapshot: %v" , err )
2040+ }
2041+ if snap .Number != 450 {
2042+ t .Errorf ("snapshot number mismatch: have %d, want 450" , snap .Number )
2043+ }
2044+ if snap .Hash != gapBlockHash {
2045+ t .Errorf ("snapshot hash mismatch: have %v, want %v" , snap .Hash , gapBlockHash )
2046+ }
2047+ }
0 commit comments