@@ -850,6 +850,71 @@ func TestBlockListenerReorgKeepLatestTailInSameBatch(t *testing.T) {
850850 mRPC .AssertExpectations (t )
851851}
852852
853+ // TestTrimToLastValidBlockRemovesOnlyInvalidSuffix covers the case where the in-memory tail
854+ // diverges from the node but an older prefix still matches. The suffix must be removed without
855+ // dropping the last matching block (regression for incorrect removal variable in the trim loop).
856+ func TestTrimToLastValidBlockRemovesInvalidTail (t * testing.T ) {
857+ h98 := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
858+ h99 := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
859+ h100 := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
860+ h101Stale := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
861+ h102Stale := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
862+ h101 := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
863+ h102 := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
864+
865+ _ , bl , mRPC , done := newTestBlockListener (t )
866+ defer done ()
867+
868+ mRPC .On ("CallRPC" , mock .Anything , mock .Anything , "eth_getBlockByNumber" , hexNumber (102 ), false ).Return (nil ).Run (func (args mock.Arguments ) {
869+ * args [1 ].(* * ethrpc.EVMBlockWithTxHashesJSONRPC ) = & ethrpc.EVMBlockWithTxHashesJSONRPC {BlockHeaderJSONRPC : ethrpc.BlockHeaderJSONRPC {
870+ Number : 102 ,
871+ Hash : h102 ,
872+ ParentHash : h101 ,
873+ }}
874+ }).Once ()
875+ mRPC .On ("CallRPC" , mock .Anything , mock .Anything , "eth_getBlockByNumber" , hexNumber (101 ), false ).Return (nil ).Run (func (args mock.Arguments ) {
876+ * args [1 ].(* * ethrpc.EVMBlockWithTxHashesJSONRPC ) = & ethrpc.EVMBlockWithTxHashesJSONRPC {BlockHeaderJSONRPC : ethrpc.BlockHeaderJSONRPC {
877+ Number : 101 ,
878+ Hash : h101 ,
879+ ParentHash : h100 ,
880+ }}
881+ }).Once ()
882+ mRPC .On ("CallRPC" , mock .Anything , mock .Anything , "eth_getBlockByNumber" , hexNumber (100 ), false ).Return (nil ).Run (func (args mock.Arguments ) {
883+ * args [1 ].(* * ethrpc.EVMBlockWithTxHashesJSONRPC ) = & ethrpc.EVMBlockWithTxHashesJSONRPC {BlockHeaderJSONRPC : ethrpc.BlockHeaderJSONRPC {
884+ Number : 100 ,
885+ Hash : h100 ,
886+ ParentHash : h99 ,
887+ }}
888+ }).Once ()
889+
890+ b99 := & ethrpc.BlockInfoJSONRPC {Number : ethtypes .HexUint64 (99 ), Hash : h99 , ParentHash : h98 }
891+ b100 := & ethrpc.BlockInfoJSONRPC {Number : ethtypes .HexUint64 (100 ), Hash : h100 , ParentHash : h99 }
892+ b101 := & ethrpc.BlockInfoJSONRPC {Number : ethtypes .HexUint64 (101 ), Hash : h101Stale , ParentHash : h100 }
893+ b102 := & ethrpc.BlockInfoJSONRPC {Number : ethtypes .HexUint64 (102 ), Hash : h102Stale , ParentHash : h101Stale }
894+
895+ bl .canonicalChainLock .Lock ()
896+ bl .canonicalChain .PushBack (b99 )
897+ bl .canonicalChain .PushBack (b100 )
898+ bl .canonicalChain .PushBack (b101 )
899+ bl .canonicalChain .PushBack (b102 )
900+
901+ lastValid := bl .trimToLastValidBlock ()
902+ bl .canonicalChainLock .Unlock ()
903+
904+ require .NotNil (t , lastValid )
905+ require .Equal (t , uint64 (100 ), lastValid .Number .Uint64 ())
906+ require .True (t , lastValid .Hash .Equals (h100 ))
907+ require .Equal (t , 2 , bl .canonicalChain .Len ())
908+
909+ front := bl .canonicalChain .Front ().Value .(* ethrpc.BlockInfoJSONRPC )
910+ require .Equal (t , uint64 (99 ), front .Number .Uint64 ())
911+ require .True (t , front .Hash .Equals (h99 ))
912+ tail := bl .canonicalChain .Back ().Value .(* ethrpc.BlockInfoJSONRPC )
913+ require .Equal (t , uint64 (100 ), tail .Number .Uint64 ())
914+ require .True (t , tail .Hash .Equals (h100 ))
915+
916+ mRPC .AssertExpectations (t )
917+ }
853918func TestBlockListenerReorgReplaceTail (t * testing.T ) {
854919
855920 block1000Hash := ethtypes .MustNewHexBytes0xPrefix (fftypes .NewRandB32 ().String ())
0 commit comments