Repository: 0xPolygon/heimdall-v2
Pull request #196: Milestones fast forward
Author: login: avalkov
Created: 2025-03-13T20:18:55Z | Merged: 2025-03-20T07:09:48Z

# Description

Added check to verify that blocks we gonna propose for milestone are on the same fork as the last finalized milestone.
Added logic to finalize bigger range of blocks if difference between last finalized milestone and execution layer tip is too big.



Diff:
diff --git a/api/heimdallv2/milestone/milestone.pulsar.go b/api/heimdallv2/milestone/milestone.pulsar.go
index 9d7de358a..c12a09c36 100644
--- a/api/heimdallv2/milestone/milestone.pulsar.go
+++ b/api/heimdallv2/milestone/milestone.pulsar.go
@@ -1227,6 +1227,7 @@ var (
 	md_MilestoneProposition                    protoreflect.MessageDescriptor
 	fd_MilestoneProposition_block_hashes       protoreflect.FieldDescriptor
 	fd_MilestoneProposition_start_block_number protoreflect.FieldDescriptor
+	fd_MilestoneProposition_parent_hash        protoreflect.FieldDescriptor
 )
 
 func init() {
@@ -1234,6 +1235,7 @@ func init() {
 	md_MilestoneProposition = File_heimdallv2_milestone_milestone_proto.Messages().ByName("MilestoneProposition")
 	fd_MilestoneProposition_block_hashes = md_MilestoneProposition.Fields().ByName("block_hashes")
 	fd_MilestoneProposition_start_block_number = md_MilestoneProposition.Fields().ByName("start_block_number")
+	fd_MilestoneProposition_parent_hash = md_MilestoneProposition.Fields().ByName("parent_hash")
 }
 
 var _ protoreflect.Message = (*fastReflection_MilestoneProposition)(nil)
@@ -1313,6 +1315,12 @@ func (x *fastReflection_MilestoneProposition) Range(f func(protoreflect.FieldDes
 			return
 		}
 	}
+	if len(x.ParentHash) != 0 {
+		value := protoreflect.ValueOfBytes(x.ParentHash)
+		if !f(fd_MilestoneProposition_parent_hash, value) {
+			return
+		}
+	}
 }
 
 // Has reports whether a field is populated.
@@ -1332,6 +1340,8 @@ func (x *fastReflection_MilestoneProposition) Has(fd protoreflect.FieldDescripto
 		return len(x.BlockHashes) != 0
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		return x.StartBlockNumber != uint64(0)
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		return len(x.ParentHash) != 0
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1352,6 +1362,8 @@ func (x *fastReflection_MilestoneProposition) Clear(fd protoreflect.FieldDescrip
 		x.BlockHashes = nil
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		x.StartBlockNumber = uint64(0)
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		x.ParentHash = nil
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1377,6 +1389,9 @@ func (x *fastReflection_MilestoneProposition) Get(descriptor protoreflect.FieldD
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		value := x.StartBlockNumber
 		return protoreflect.ValueOfUint64(value)
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		value := x.ParentHash
+		return protoreflect.ValueOfBytes(value)
 	default:
 		if descriptor.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1403,6 +1418,8 @@ func (x *fastReflection_MilestoneProposition) Set(fd protoreflect.FieldDescripto
 		x.BlockHashes = *clv.list
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		x.StartBlockNumber = value.Uint()
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		x.ParentHash = value.Bytes()
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1431,6 +1448,8 @@ func (x *fastReflection_MilestoneProposition) Mutable(fd protoreflect.FieldDescr
 		return protoreflect.ValueOfList(value)
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		panic(fmt.Errorf("field start_block_number of message heimdallv2.milestone.MilestoneProposition is not mutable"))
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		panic(fmt.Errorf("field parent_hash of message heimdallv2.milestone.MilestoneProposition is not mutable"))
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1449,6 +1468,8 @@ func (x *fastReflection_MilestoneProposition) NewField(fd protoreflect.FieldDesc
 		return protoreflect.ValueOfList(&_MilestoneProposition_1_list{list: &list})
 	case "heimdallv2.milestone.MilestoneProposition.start_block_number":
 		return protoreflect.ValueOfUint64(uint64(0))
+	case "heimdallv2.milestone.MilestoneProposition.parent_hash":
+		return protoreflect.ValueOfBytes(nil)
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.MilestoneProposition"))
@@ -1527,6 +1548,10 @@ func (x *fastReflection_MilestoneProposition) ProtoMethods() *protoiface.Methods
 		if x.StartBlockNumber != 0 {
 			n += 1 + runtime.Sov(uint64(x.StartBlockNumber))
 		}
+		l = len(x.ParentHash)
+		if l > 0 {
+			n += 1 + l + runtime.Sov(uint64(l))
+		}
 		if x.unknownFields != nil {
 			n += len(x.unknownFields)
 		}
@@ -1556,6 +1581,13 @@ func (x *fastReflection_MilestoneProposition) ProtoMethods() *protoiface.Methods
 			i -= len(x.unknownFields)
 			copy(dAtA[i:], x.unknownFields)
 		}
+		if len(x.ParentHash) > 0 {
+			i -= len(x.ParentHash)
+			copy(dAtA[i:], x.ParentHash)
+			i = runtime.EncodeVarint(dAtA, i, uint64(len(x.ParentHash)))
+			i--
+			dAtA[i] = 0x1a
+		}
 		if x.StartBlockNumber != 0 {
 			i = runtime.EncodeVarint(dAtA, i, uint64(x.StartBlockNumber))
 			i--
@@ -1670,6 +1702,40 @@ func (x *fastReflection_MilestoneProposition) ProtoMethods() *protoiface.Methods
 						break
 					}
 				}
+			case 3:
+				if wireType != 2 {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ParentHash", wireType)
+				}
+				var byteLen int
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
+					}
+					if iNdEx >= l {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					byteLen |= int(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				if byteLen < 0 {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
+				}
+				postIndex := iNdEx + byteLen
+				if postIndex < 0 {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
+				}
+				if postIndex > l {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
+				}
+				x.ParentHash = append(x.ParentHash[:0], dAtA[iNdEx:postIndex]...)
+				if x.ParentHash == nil {
+					x.ParentHash = []byte{}
+				}
+				iNdEx = postIndex
 			default:
 				iNdEx = preIndex
 				skippy, err := runtime.Skip(dAtA[iNdEx:])
@@ -1708,12 +1774,16 @@ func (x *fastReflection_MilestoneProposition) ProtoMethods() *protoiface.Methods
 var (
 	md_Params                                  protoreflect.MessageDescriptor
 	fd_Params_max_milestone_proposition_length protoreflect.FieldDescriptor
+	fd_Params_ff_milestone_threshold           protoreflect.FieldDescriptor
+	fd_Params_ff_milestone_block_interval      protoreflect.FieldDescriptor
 )
 
 func init() {
 	file_heimdallv2_milestone_milestone_proto_init()
 	md_Params = File_heimdallv2_milestone_milestone_proto.Messages().ByName("Params")
 	fd_Params_max_milestone_proposition_length = md_Params.Fields().ByName("max_milestone_proposition_length")
+	fd_Params_ff_milestone_threshold = md_Params.Fields().ByName("ff_milestone_threshold")
+	fd_Params_ff_milestone_block_interval = md_Params.Fields().ByName("ff_milestone_block_interval")
 }
 
 var _ protoreflect.Message = (*fastReflection_Params)(nil)
@@ -1787,6 +1857,18 @@ func (x *fastReflection_Params) Range(f func(protoreflect.FieldDescriptor, proto
 			return
 		}
 	}
+	if x.FfMilestoneThreshold != uint64(0) {
+		value := protoreflect.ValueOfUint64(x.FfMilestoneThreshold)
+		if !f(fd_Params_ff_milestone_threshold, value) {
+			return
+		}
+	}
+	if x.FfMilestoneBlockInterval != uint64(0) {
+		value := protoreflect.ValueOfUint64(x.FfMilestoneBlockInterval)
+		if !f(fd_Params_ff_milestone_block_interval, value) {
+			return
+		}
+	}
 }
 
 // Has reports whether a field is populated.
@@ -1804,6 +1886,10 @@ func (x *fastReflection_Params) Has(fd protoreflect.FieldDescriptor) bool {
 	switch fd.FullName() {
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		return x.MaxMilestonePropositionLength != uint64(0)
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		return x.FfMilestoneThreshold != uint64(0)
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		return x.FfMilestoneBlockInterval != uint64(0)
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1822,6 +1908,10 @@ func (x *fastReflection_Params) Clear(fd protoreflect.FieldDescriptor) {
 	switch fd.FullName() {
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		x.MaxMilestonePropositionLength = uint64(0)
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		x.FfMilestoneThreshold = uint64(0)
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		x.FfMilestoneBlockInterval = uint64(0)
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1841,6 +1931,12 @@ func (x *fastReflection_Params) Get(descriptor protoreflect.FieldDescriptor) pro
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		value := x.MaxMilestonePropositionLength
 		return protoreflect.ValueOfUint64(value)
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		value := x.FfMilestoneThreshold
+		return protoreflect.ValueOfUint64(value)
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		value := x.FfMilestoneBlockInterval
+		return protoreflect.ValueOfUint64(value)
 	default:
 		if descriptor.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1863,6 +1959,10 @@ func (x *fastReflection_Params) Set(fd protoreflect.FieldDescriptor, value proto
 	switch fd.FullName() {
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		x.MaxMilestonePropositionLength = value.Uint()
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		x.FfMilestoneThreshold = value.Uint()
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		x.FfMilestoneBlockInterval = value.Uint()
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1885,6 +1985,10 @@ func (x *fastReflection_Params) Mutable(fd protoreflect.FieldDescriptor) protore
 	switch fd.FullName() {
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		panic(fmt.Errorf("field max_milestone_proposition_length of message heimdallv2.milestone.Params is not mutable"))
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		panic(fmt.Errorf("field ff_milestone_threshold of message heimdallv2.milestone.Params is not mutable"))
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		panic(fmt.Errorf("field ff_milestone_block_interval of message heimdallv2.milestone.Params is not mutable"))
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1900,6 +2004,10 @@ func (x *fastReflection_Params) NewField(fd protoreflect.FieldDescriptor) protor
 	switch fd.FullName() {
 	case "heimdallv2.milestone.Params.max_milestone_proposition_length":
 		return protoreflect.ValueOfUint64(uint64(0))
+	case "heimdallv2.milestone.Params.ff_milestone_threshold":
+		return protoreflect.ValueOfUint64(uint64(0))
+	case "heimdallv2.milestone.Params.ff_milestone_block_interval":
+		return protoreflect.ValueOfUint64(uint64(0))
 	default:
 		if fd.IsExtension() {
 			panic(fmt.Errorf("proto3 declared messages do not support extensions: heimdallv2.milestone.Params"))
@@ -1972,6 +2080,12 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods {
 		if x.MaxMilestonePropositionLength != 0 {
 			n += 1 + runtime.Sov(uint64(x.MaxMilestonePropositionLength))
 		}
+		if x.FfMilestoneThreshold != 0 {
+			n += 1 + runtime.Sov(uint64(x.FfMilestoneThreshold))
+		}
+		if x.FfMilestoneBlockInterval != 0 {
+			n += 1 + runtime.Sov(uint64(x.FfMilestoneBlockInterval))
+		}
 		if x.unknownFields != nil {
 			n += len(x.unknownFields)
 		}
@@ -2001,6 +2115,16 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods {
 			i -= len(x.unknownFields)
 			copy(dAtA[i:], x.unknownFields)
 		}
+		if x.FfMilestoneBlockInterval != 0 {
+			i = runtime.EncodeVarint(dAtA, i, uint64(x.FfMilestoneBlockInterval))
+			i--
+			dAtA[i] = 0x18
+		}
+		if x.FfMilestoneThreshold != 0 {
+			i = runtime.EncodeVarint(dAtA, i, uint64(x.FfMilestoneThreshold))
+			i--
+			dAtA[i] = 0x10
+		}
 		if x.MaxMilestonePropositionLength != 0 {
 			i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxMilestonePropositionLength))
 			i--
@@ -2074,6 +2198,44 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods {
 						break
 					}
 				}
+			case 2:
+				if wireType != 0 {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field FfMilestoneThreshold", wireType)
+				}
+				x.FfMilestoneThreshold = 0
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
+					}
+					if iNdEx >= l {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					x.FfMilestoneThreshold |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+			case 3:
+				if wireType != 0 {
+					return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field FfMilestoneBlockInterval", wireType)
+				}
+				x.FfMilestoneBlockInterval = 0
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
+					}
+					if iNdEx >= l {
+						return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					x.FfMilestoneBlockInterval |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
 			default:
 				iNdEx = preIndex
 				skippy, err := runtime.Skip(dAtA[iNdEx:])
@@ -2247,6 +2409,7 @@ type MilestoneProposition struct {
 
 	BlockHashes      [][]byte `protobuf:"bytes,1,rep,name=block_hashes,json=blockHashes,proto3" json:"block_hashes,omitempty"`
 	StartBlockNumber uint64   `protobuf:"varint,2,opt,name=start_block_number,json=startBlockNumber,proto3" json:"start_block_number,omitempty"`
+	ParentHash       []byte   `protobuf:"bytes,3,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"`
 }
 
 func (x *MilestoneProposition) Reset() {
@@ -2283,12 +2446,21 @@ func (x *MilestoneProposition) GetStartBlockNumber() uint64 {
 	return 0
 }
 
+func (x *MilestoneProposition) GetParentHash() []byte {
+	if x != nil {
+		return x.ParentHash
+	}
+	return nil
+}
+
 type Params struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
 	MaxMilestonePropositionLength uint64 `protobuf:"varint,1,opt,name=max_milestone_proposition_length,json=maxMilestonePropositionLength,proto3" json:"max_milestone_proposition_length,omitempty"`
+	FfMilestoneThreshold          uint64 `protobuf:"varint,2,opt,name=ff_milestone_threshold,json=ffMilestoneThreshold,proto3" json:"ff_milestone_threshold,omitempty"`
+	FfMilestoneBlockInterval      uint64 `protobuf:"varint,3,opt,name=ff_milestone_block_interval,json=ffMilestoneBlockInterval,proto3" json:"ff_milestone_block_interval,omitempty"`
 }
 
 func (x *Params) Reset() {
@@ -2318,6 +2490,20 @@ func (x *Params) GetMaxMilestonePropositionLength() uint64 {
 	return 0
 }
 
+func (x *Params) GetFfMilestoneThreshold() uint64 {
+	if x != nil {
+		return x.FfMilestoneThreshold
+	}
+	return 0
+}
+
+func (x *Params) GetFfMilestoneBlockInterval() uint64 {
+	if x != nil {
+		return x.FfMilestoneBlockInterval
+	}
+	return 0
+}
+
 var File_heimdallv2_milestone_milestone_proto protoreflect.FileDescriptor
 
 var file_heimdallv2_milestone_milestone_proto_rawDesc = []byte{
@@ -2351,36 +2537,46 @@ var file_heimdallv2_milestone_milestone_proto_rawDesc = []byte{
 	0x1f, 0x01, 0x22, 0x37, 0x0a, 0x0e, 0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x43,
 	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20,
 	0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e,
-	0x74, 0x3a, 0x08, 0x88, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0x7f, 0x0a, 0x14, 0x4d,
-	0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73,
-	0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
-	0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x33, 0x0a,
-	0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d,
-	0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
-	0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62,
-	0x65, 0x72, 0x3a, 0x08, 0x88, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0x62, 0x0a, 0x06,
-	0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x4e, 0x0a, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x69,
-	0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69, 0x74,
-	0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
-	0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x1d, 0x6d, 0x61, 0x78, 0x4d, 0x69, 0x6c, 0x65,
-	0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
-	0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x08, 0x88, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01,
-	0x42, 0xd6, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c,
-	0x6c, 0x76, 0x32, 0x2e, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x42, 0x0e, 0x4d,
-	0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
-	0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x78, 0x50, 0x6f,
-	0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x2f, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x2d, 0x76,
-	0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32,
-	0x2f, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0xa2, 0x02, 0x03, 0x48, 0x4d, 0x58,
-	0xaa, 0x02, 0x14, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x2e, 0x4d, 0x69,
-	0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0xca, 0x02, 0x14, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61,
-	0x6c, 0x6c, 0x76, 0x32, 0x5c, 0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0xe2, 0x02,
-	0x20, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x5c, 0x4d, 0x69, 0x6c, 0x65,
-	0x73, 0x74, 0x6f, 0x6e, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0xea, 0x02, 0x15, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x3a, 0x3a,
-	0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x74, 0x3a, 0x08, 0x88, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xa7, 0x01, 0x0a, 0x14,
+	0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69,
+	0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61,
+	0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a,
+	0x01, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x33,
+	0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75,
+	0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a,
+	0x01, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61,
+	0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52,
+	0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x3a, 0x08, 0x88, 0xa0, 0x1f,
+	0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xe5, 0x01, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
+	0x12, 0x4e, 0x0a, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e,
+	0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x65,
+	0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a,
+	0x01, 0x52, 0x1d, 0x6d, 0x61, 0x78, 0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50,
+	0x72, 0x6f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68,
+	0x12, 0x3b, 0x0a, 0x16, 0x66, 0x66, 0x5f, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65,
+	0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
+	0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x14, 0x66, 0x66, 0x4d, 0x69, 0x6c, 0x65, 0x73,
+	0x74, 0x6f, 0x6e, 0x65, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x44, 0x0a,
+	0x1b, 0x66, 0x66, 0x5f, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x5f, 0x62, 0x6c,
+	0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x18, 0x66, 0x66, 0x4d, 0x69, 0x6c,
+	0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72,
+	0x76, 0x61, 0x6c, 0x3a, 0x08, 0x88, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x42, 0xd6, 0x01,
+	0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32,
+	0x2e, 0x6d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x42, 0x0e, 0x4d, 0x69, 0x6c, 0x65,
+	0x73, 0x74, 0x6f, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x39, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x78, 0x50, 0x6f, 0x6c, 0x79, 0x67,
+	0x6f, 0x6e, 0x2f, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x2d, 0x76, 0x32, 0x2f, 0x61,
+	0x70, 0x69, 0x2f, 0x68, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x2f, 0x6d, 0x69,
+	0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0xa2, 0x02, 0x03, 0x48, 0x4d, 0x58, 0xaa, 0x02, 0x14,
+	0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x2e, 0x4d, 0x69, 0x6c, 0x65, 0x73,
+	0x74, 0x6f, 0x6e, 0x65, 0xca, 0x02, 0x14, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76,
+	0x32, 0x5c, 0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0xe2, 0x02, 0x20, 0x48, 0x65,
+	0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x5c, 0x4d, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f,
+	0x6e, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02,
+	0x15, 0x48, 0x65, 0x69, 0x6d, 0x64, 0x61, 0x6c, 0x6c, 0x76, 0x32, 0x3a, 0x3a, 0x4d, 0x69, 0x6c,
+	0x65, 0x73, 0x74, 0x6f, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/app/abci.go b/app/abci.go
index be3adb37a..ab2b26e6c 100644
--- a/app/abci.go
+++ b/app/abci.go
@@ -33,9 +33,6 @@ func (app *HeimdallApp) NewPrepareProposalHandler() sdk.PrepareProposalHandler {
 
 		if err := ValidateNonRpVoteExtensions(ctx, req.Height, req.LocalLastCommit.Votes, app.StakeKeeper, app.ChainManagerKeeper, app.CheckpointKeeper, &app.caller, logger); err != nil {
 			logger.Error("Error occurred while validating non-rp VEs in PrepareProposal", err)
-			if !errors.Is(err, borTypes.ErrFailedToQueryBor) {
-				return nil, err
-			}
 		}
 
 		// prepare the proposal with the vote extensions and the validators set's votes
@@ -263,10 +260,6 @@ func (app *HeimdallApp) ExtendVoteHandler() sdk.ExtendVoteHandler {
 
 		if err := ValidateNonRpVoteExtension(ctx, req.Height, nonRpVoteExt, app.ChainManagerKeeper, app.CheckpointKeeper, &app.caller); err != nil {
 			logger.Error("Error occurred while validating non-rp vote extension", "error", err)
-			if errors.Is(err, borTypes.ErrFailedToQueryBor) {
-				return &abci.ResponseExtendVote{VoteExtension: bz, NonRpExtension: dummyVoteExt}, nil
-			}
-			return nil, err
 		}
 
 		return &abci.ResponseExtendVote{VoteExtension: bz, NonRpExtension: nonRpVoteExt}, nil
@@ -316,9 +309,6 @@ func (app *HeimdallApp) VerifyVoteExtensionHandler() sdk.VerifyVoteExtensionHand
 
 		if err := ValidateNonRpVoteExtension(ctx, req.Height, req.NonRpVoteExtension, app.ChainManagerKeeper, app.CheckpointKeeper, &app.caller); err != nil {
 			logger.Error("ALERT, NON-RP VOTE EXTENSION REJECTED. THIS SHOULD NOT HAPPEN; THE VALIDATOR COULD BE MALICIOUS!", "validator", valAddr, "error", err)
-			if !errors.Is(err, borTypes.ErrFailedToQueryBor) {
-				return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT}, nil
-			}
 		}
 
 		if err := milestoneAbci.ValidateMilestoneProposition(ctx, &app.MilestoneKeeper, voteExtension.MilestoneProposition); err != nil {
@@ -389,6 +379,7 @@ func (app *HeimdallApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlo
 	}
 
 	var lastEndBlock *uint64 = nil
+	var lastEndHash []byte
 	if hasMilestone {
 		lastMilestone, err := app.MilestoneKeeper.GetLastMilestone(ctx)
 		if err != nil {
@@ -396,9 +387,16 @@ func (app *HeimdallApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlo
 			return nil, err
 		}
 		lastEndBlock = &lastMilestone.EndBlock
+		lastEndHash = lastMilestone.Hash
 	}
 
-	majorityMilestone, aggregatedProposers, proposer, err := milestoneAbci.GetMajorityMilestoneProposition(validatorSet, extVoteInfo, logger, lastEndBlock)
+	majorityMilestone, aggregatedProposers, proposer, err := milestoneAbci.GetMajorityMilestoneProposition(
+		validatorSet,
+		extVoteInfo,
+		logger,
+		lastEndBlock,
+		lastEndHash,
+	)
 	if err != nil {
 		logger.Error("Error occurred while getting majority milestone proposition", "error", err)
 		return nil, err
@@ -423,7 +421,12 @@ func (app *HeimdallApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlo
 
 		addMilestoneCtx, msCache := app.cacheTxContext(ctx)
 
-		logger.Debug("Adding milestone", "hashes", strutil.HashesToString(majorityMilestone.BlockHashes), "startBlock", majorityMilestone.StartBlockNumber, "endBlock", majorityMilestone.StartBlockNumber+uint64(len(majorityMilestone.BlockHashes)-1), "proposer", proposer)
+		logger.Debug("Adding milestone", "hashes",
+			strutil.HashesToString(majorityMilestone.BlockHashes),
+			"startBlock", majorityMilestone.StartBlockNumber,
+			"endBlock", majorityMilestone.StartBlockNumber+uint64(len(majorityMilestone.BlockHashes)-1),
+			"proposer", proposer,
+		)
 
 		if err := app.MilestoneKeeper.AddMilestone(addMilestoneCtx, milestoneTypes.Milestone{
 			Proposer:    proposer,
@@ -438,11 +441,6 @@ func (app *HeimdallApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlo
 			return nil, err
 		}
 
-		if err = app.MilestoneKeeper.SetMilestoneBlockNumber(addMilestoneCtx, ctx.BlockHeight()); err != nil {
-			logger.Error("error in setting milestone block number", "error", err)
-			return nil, err
-		}
-
 		msCache.Write()
 	}
 
diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml
index f46772109..184555d99 100644
--- a/client/docs/swagger-ui/swagger.yaml
+++ b/client/docs/swagger-ui/swagger.yaml
@@ -2136,6 +2136,12 @@ paths:
                   max_milestone_proposition_length:
                     type: string
                     format: uint64
+                  ff_milestone_threshold:
+                    type: string
+                    format: uint64
+                  ff_milestone_block_interval:
+                    type: string
+                    format: uint64
         default:
           description: An unexpected error response.
           schema:
@@ -4446,6 +4452,12 @@ definitions:
       max_milestone_proposition_length:
         type: string
         format: uint64
+      ff_milestone_threshold:
+        type: string
+        format: uint64
+      ff_milestone_block_interval:
+        type: string
+        format: uint64
   heimdallv2.milestone.QueryCountResponse:
     type: object
     properties:
@@ -4509,6 +4521,12 @@ definitions:
           max_milestone_proposition_length:
             type: string
             format: uint64
+          ff_milestone_threshold:
+            type: string
+            format: uint64
+          ff_milestone_block_interval:
+            type: string
+            format: uint64
   heimdallv2.stake.QueryCurrentValidatorSetResponse:
     type: object
     properties:
diff --git a/cmd/heimdalld/cmd/testdata/migrated_dump-genesis.json b/cmd/heimdalld/cmd/testdata/migrated_dump-genesis.json
index e16c9dc0e..f4f90a39b 100644
--- a/cmd/heimdalld/cmd/testdata/migrated_dump-genesis.json
+++ b/cmd/heimdalld/cmd/testdata/migrated_dump-genesis.json
@@ -1559,7 +1559,9 @@
     },
     "milestone": {
       "params": {
-        "max_milestone_proposition_length": "10"
+        "max_milestone_proposition_length": "10",
+        "ff_milestone_threshold": "1000",
+        "ff_milestone_block_interval": "100"
       }
     },
     "sidechannel": {
diff --git a/proto/heimdallv2/milestone/milestone.proto b/proto/heimdallv2/milestone/milestone.proto
index 4e8753d23..0491ce743 100644
--- a/proto/heimdallv2/milestone/milestone.proto
+++ b/proto/heimdallv2/milestone/milestone.proto
@@ -34,10 +34,13 @@ message MilestoneProposition {
 
   repeated bytes block_hashes = 1 [ (amino.dont_omitempty) = true ];
   uint64 start_block_number = 2 [ (amino.dont_omitempty) = true ];
+  bytes parent_hash = 3 [ (amino.dont_omitempty) = true ];
 }
 
 message Params {
   option (gogoproto.equal) = true;
   option (gogoproto.goproto_getters) = true;
   uint64 max_milestone_proposition_length = 1 [ (amino.dont_omitempty) = true ];
+  uint64 ff_milestone_threshold = 2 [ (amino.dont_omitempty) = true ];
+  uint64 ff_milestone_block_interval = 3 [ (amino.dont_omitempty) = true ];
 }
diff --git a/x/milestone/abci/abci.go b/x/milestone/abci/abci.go
index 21fd66d33..19b91b2ce 100644
--- a/x/milestone/abci/abci.go
+++ b/x/milestone/abci/abci.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
+	"math/big"
 	"sort"
 
 	"cosmossdk.io/log"
@@ -27,21 +28,30 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	logger := ctx.Logger()
+	propStartBlock := uint64(0)
 
-	lastMilestoneBlockNumber, err := milestoneKeeper.GetMilestoneBlockNumber(ctx)
-	if err != nil {
-		return nil, err
-	}
+	var lastMilestoneHash []byte
+	var lastMilestoneBlockNumber uint64
 
-	blocksSinceLastMilestone := reqBlock - lastMilestoneBlockNumber
+	if milestone != nil {
+		propStartBlock = milestone.EndBlock + 1
 
-	logger.Debug("blocksSinceLastMilestone", "blocksSinceLastMilestone", blocksSinceLastMilestone)
+		latestHeader, err := contractCaller.GetBorChainBlock(ctx, nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get latest header")
+		}
 
-	propStartBlock := uint64(0)
+		params, err := milestoneKeeper.GetParams(ctx)
+		if err != nil {
+			return nil, err
+		}
 
-	if milestone != nil {
-		propStartBlock = milestone.EndBlock + 1
+		if isFastForwardMilestone(latestHeader.Number.Uint64(), milestone.EndBlock, params.FfMilestoneThreshold) {
+			propStartBlock = getFastForwardMilestoneStartBlock(latestHeader.Number.Uint64(), milestone.EndBlock, params.FfMilestoneBlockInterval)
+		}
+
+		lastMilestoneHash = milestone.Hash
+		lastMilestoneBlockNumber = milestone.EndBlock
 	}
 
 	params, err := milestoneKeeper.GetParams(ctx)
@@ -49,20 +59,40 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	blockHashes, err := getBlockHashes(ctx, propStartBlock, params.MaxMilestonePropositionLength, contractCaller)
+	parentHash, blockHashes, err := getBlockHashes(ctx, propStartBlock, params.MaxMilestonePropositionLength, lastMilestoneHash, lastMilestoneBlockNumber, contractCaller)
 	if err != nil {
 		return nil, err
 	}
 
+	if err := validateMilestonePropositionFork(parentHash, lastMilestoneHash); err != nil {
+		return nil, err
+	}
+
 	milestoneProp := &types.MilestoneProposition{
 		BlockHashes:      blockHashes,
 		StartBlockNumber: propStartBlock,
+		ParentHash:       parentHash,
 	}
 
 	return milestoneProp, nil
 }
 
-func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extVoteInfo []abciTypes.ExtendedVoteInfo, logger log.Logger, lastEndBlock *uint64) (*types.MilestoneProposition, []byte, string, error) {
+func isFastForwardMilestone(latestHeaderNumber, latestMilestoneEndBlock, ffMilestoneThreshold uint64) bool {
+	return latestHeaderNumber > latestMilestoneEndBlock && latestHeaderNumber-latestMilestoneEndBlock > ffMilestoneThreshold
+}
+
+func getFastForwardMilestoneStartBlock(latestHeaderNumber, latestMilestoneEndBlock, ffMilestoneBlockInterval uint64) uint64 {
+	latestHeaderMilestoneDistanceInBlocks := ((latestHeaderNumber - latestMilestoneEndBlock) / ffMilestoneBlockInterval) * ffMilestoneBlockInterval
+	return latestMilestoneEndBlock + latestHeaderMilestoneDistanceInBlocks + 1
+}
+
+func GetMajorityMilestoneProposition(
+	validatorSet *stakeTypes.ValidatorSet,
+	extVoteInfo []abciTypes.ExtendedVoteInfo,
+	logger log.Logger,
+	lastEndBlock *uint64,
+	lastEndBlockHash []byte,
+) (*types.MilestoneProposition, []byte, string, error) {
 	ac := address.HexCodec{}
 
 	// Track voting power per block number
@@ -72,6 +102,8 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	validatorVotes := make(map[string]map[uint64][]byte) // validator -> block -> hash
 	validatorAddresses := make(map[string][]byte)
 	valAddressToVotingPower := make(map[string]int64)
+	parentHashes := make(map[string]struct{})
+	parentHashToVotingPower := make(map[string]int64)
 
 	// Track which validators we've already processed to prevent duplicate votes
 	processedValidators := make(map[string]bool)
@@ -79,6 +111,10 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	totalVotingPower := validatorSet.GetTotalVotingPower()
 	majorityVP := totalVotingPower*2/3 + 1
 
+	getParentChildKey := func(parent, child string) string {
+		return fmt.Sprintf("%s-%s", parent, child)
+	}
+
 	// First pass - collect all votes
 	for _, vote := range extVoteInfo {
 		if vote.BlockIdFlag != cmtTypes.BlockIDFlagCommit {
@@ -119,6 +155,7 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 
 		prop := voteExtension.MilestoneProposition
 		for i, blockHash := range prop.BlockHashes {
+
 			blockNum := prop.StartBlockNumber + uint64(i)
 
 			// Record this validator's vote for this block
@@ -142,7 +179,11 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 				blockVotingPower[blockNum] = blockHashVotes[blockNum][hashStr]
 				blockToHash[blockNum] = blockHash
 			}
+
+			key := getParentChildKey(common.BytesToHash(prop.ParentHash).String(), common.BytesToHash(blockHash).String())
+			parentHashToVotingPower[key] += validator.VotingPower
 		}
+		parentHashes[common.BytesToHash(prop.ParentHash).String()] = struct{}{}
 	}
 
 	// Find blocks with majority support - use a slice for deterministic ordering
@@ -166,11 +207,39 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 		return nil, nil, "", nil
 	}
 
+	var majorityParentHash string
+	isParentHashMajority := false
+
+	for parentHash := range parentHashes {
+		key := getParentChildKey(parentHash, common.BytesToHash(blockToHash[majorityBlocks[0]]).String())
+		if parentHashToVotingPower[key] >= majorityVP {
+			isParentHashMajority = true
+			majorityParentHash = parentHash
+			break
+		}
+	}
+
+	if !isParentHashMajority {
+		logger.Debug("No parent hash found with majority support")
+		return nil, nil, "", nil
+	}
+
+	if majorityParentHash != common.BytesToHash(lastEndBlockHash).String() {
+		logger.Debug("Parent hash does not match last end block hash",
+			"majorityParentHash", majorityParentHash,
+			"lastEndBlockHash", common.BytesToHash(lastEndBlockHash).String())
+		return nil, nil, "", nil
+	}
+
 	startBlock := uint64(0)
 
 	// Check if we have a block that starts exactly from lastEndBlock + 1
 	if lastEndBlock != nil {
 		startBlock = *lastEndBlock + 1
+
+		if majorityBlocks[0] > startBlock {
+			startBlock = majorityBlocks[0]
+		}
 	}
 
 	// Check if startBlock is in majorityBlocks
@@ -276,19 +345,41 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([]byte, [][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
-		return nil, fmt.Errorf("failed to get headers")
+		return nil, nil, fmt.Errorf("failed to get headers")
+	}
+
+	var parentHash []byte
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		parentHash = headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
+			header, err := contractCaller.GetBorChainBlock(ctx, big.NewInt(int64(lastMilestoneBlock+1)))
+			if err != nil {
+				return nil, nil, fmt.Errorf("failed to get headers")
+			}
+
+			parentHash = header.ParentHash.Bytes()
+		}
 	}
 
 	for _, h := range headers {
 		result = append(result, h.Hash().Bytes())
 	}
 
-	return result, nil
+	return parentHash, result, nil
+}
+
+func validateMilestonePropositionFork(parentHash []byte, lastMilestoneHash []byte) error {
+	if len(parentHash) > 0 && len(lastMilestoneHash) > 0 {
+		if !bytes.Equal(parentHash, lastMilestoneHash) {
+			return fmt.Errorf("first block parent hash does not match last milestone hash")
+		}
+	}
+	return nil
 }
 
 func ValidateMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, milestoneProp *types.MilestoneProposition) error {
diff --git a/x/milestone/abci/abci_test.go b/x/milestone/abci/abci_test.go
new file mode 100644
index 000000000..5318aabf7
--- /dev/null
+++ b/x/milestone/abci/abci_test.go
@@ -0,0 +1,122 @@
+package abci
+
+import "testing"
+
+func TestIsFastForwardMilestone(t *testing.T) {
+	tests := []struct {
+		name                    string
+		latestHeaderNumber      uint64
+		latestMilestoneEndBlock uint64
+		ffMilestoneThreshold    uint64
+		expected                bool
+	}{
+		{
+			name:                    "Header equals milestone block",
+			latestHeaderNumber:      100,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    0,
+			expected:                false,
+		},
+		{
+			name:                    "Header less than milestone block",
+			latestHeaderNumber:      90,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    0,
+			expected:                false,
+		},
+		{
+			name:                    "Difference equals threshold",
+			latestHeaderNumber:      105,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    5,
+			expected:                false, // because 105-100 == 5 (not greater than 5)
+		},
+		{
+			name:                    "Difference less than threshold",
+			latestHeaderNumber:      110,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    15,
+			expected:                false,
+		},
+		{
+			name:                    "Difference greater than threshold",
+			latestHeaderNumber:      110,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    5,
+			expected:                true,
+		},
+		{
+			name:                    "Threshold zero, header greater than milestone",
+			latestHeaderNumber:      101,
+			latestMilestoneEndBlock: 100,
+			ffMilestoneThreshold:    0,
+			expected:                true,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			result := isFastForwardMilestone(tc.latestHeaderNumber, tc.latestMilestoneEndBlock, tc.ffMilestoneThreshold)
+			if result != tc.expected {
+				t.Errorf("isFastForwardMilestone(%d, %d, %d) = %v; expected %v",
+					tc.latestHeaderNumber, tc.latestMilestoneEndBlock, tc.ffMilestoneThreshold, result, tc.expected)
+			}
+		})
+	}
+}
+
+func TestGetFastForwardMilestoneStartBlock(t *testing.T) {
+	tests := []struct {
+		name                     string
+		latestHeaderNumber       uint64
+		latestMilestoneEndBlock  uint64
+		ffMilestoneBlockInterval uint64
+		expected                 uint64
+	}{
+		{
+			name:                     "Exact multiple",
+			latestHeaderNumber:       150,
+			latestMilestoneEndBlock:  100,
+			ffMilestoneBlockInterval: 10,
+			expected:                 151, // (150-100)/10=5*10=50, then 100+50+1 = 151
+		},
+		{
+			name:                     "Not an exact multiple",
+			latestHeaderNumber:       153,
+			latestMilestoneEndBlock:  100,
+			ffMilestoneBlockInterval: 10,
+			expected:                 151, // (153-100)=53/10=5*10=50, then 100+50+1 = 151
+		},
+		{
+			name:                     "Zero difference",
+			latestHeaderNumber:       100,
+			latestMilestoneEndBlock:  100,
+			ffMilestoneBlockInterval: 10,
+			expected:                 101, // 0/10=0, then 100+0+1 = 101
+		},
+		{
+			name:                     "Interval equals 1",
+			latestHeaderNumber:       150,
+			latestMilestoneEndBlock:  100,
+			ffMilestoneBlockInterval: 1,
+			expected:                 151, // every block counts; 150-100=50, then 100+50+1 = 151
+		},
+		{
+			name:                     "Interval larger than difference",
+			latestHeaderNumber:       105,
+			latestMilestoneEndBlock:  100,
+			ffMilestoneBlockInterval: 10,
+			expected:                 101, // (5/10=0) then 100+0+1 = 101
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			result := getFastForwardMilestoneStartBlock(tc.latestHeaderNumber, tc.latestMilestoneEndBlock, tc.ffMilestoneBlockInterval)
+			if result != tc.expected {
+				t.Errorf("getFastForwardMilestoneStartBlock(%d, %d, %d) = %d; expected %d",
+					tc.latestHeaderNumber, tc.latestMilestoneEndBlock, tc.ffMilestoneBlockInterval, result, tc.expected)
+			}
+		})
+	}
+}
diff --git a/x/milestone/keeper/keeper.go b/x/milestone/keeper/keeper.go
index a54138c10..2051ea8a8 100644
--- a/x/milestone/keeper/keeper.go
+++ b/x/milestone/keeper/keeper.go
@@ -28,10 +28,9 @@ type Keeper struct {
 
 	IContractCaller helper.IContractCaller
 
-	milestone   collections.Map[uint64, types.Milestone]
-	params      collections.Item[types.Params]
-	blockNumber collections.Item[int64]
-	count       collections.Item[uint64]
+	milestone collections.Map[uint64, types.Milestone]
+	params    collections.Item[types.Params]
+	count     collections.Item[uint64]
 }
 
 // NewKeeper creates a new milestone Keeper instance
@@ -59,10 +58,9 @@ func NewKeeper(
 		cdc:             cdc,
 		IContractCaller: contractCaller,
 
-		milestone:   collections.NewMap(sb, types.MilestoneMapPrefixKey, "milestone", collections.Uint64Key, codec.CollValue[types.Milestone](cdc)),
-		params:      collections.NewItem(sb, types.ParamsPrefixKey, "params", codec.CollValue[types.Params](cdc)),
-		count:       collections.NewItem(sb, types.CountPrefixKey, "count", collections.Uint64Value),
-		blockNumber: collections.NewItem(sb, types.BlockNumberPrefixKey, "block_number", collections.Int64Value),
+		milestone: collections.NewMap(sb, types.MilestoneMapPrefixKey, "milestone", collections.Uint64Key, codec.CollValue[types.Milestone](cdc)),
+		params:    collections.NewItem(sb, types.ParamsPrefixKey, "params", codec.CollValue[types.Params](cdc)),
+		count:     collections.NewItem(sb, types.CountPrefixKey, "count", collections.Uint64Value),
 	}
 
 	// build the schema and set it in the keeper
@@ -224,35 +222,3 @@ func (k *Keeper) GetMilestoneCount(ctx context.Context) (uint64, error) {
 
 	return count, nil
 }
-
-// SetMilestoneBlockNumber set the block number when the latest milestone enter the handler
-func (k *Keeper) SetMilestoneBlockNumber(ctx context.Context, number int64) error {
-	err := k.blockNumber.Set(ctx, number)
-	if err != nil {
-		k.Logger(ctx).Error("error while setting block number in store", "err", err)
-		return err
-	}
-
-	return nil
-}
-
-// GetMilestoneBlockNumber returns the block number when the latest milestone enter the handler
-func (k *Keeper) GetMilestoneBlockNumber(ctx context.Context) (int64, error) {
-	doExist, err := k.blockNumber.Has(ctx)
-	if err != nil {
-		k.Logger(ctx).Error("error while checking the existence of block number in store", "err", err)
-		return 0, err
-	}
-
-	if !doExist {
-		return 0, nil
-	}
-
-	number, err := k.blockNumber.Get(ctx)
-	if err != nil {
-		k.Logger(ctx).Error("error while fetching block number from store", "err", err)
-		return 0, err
-	}
-
-	return number, nil
-}
diff --git a/x/milestone/types/genesis.go b/x/milestone/types/genesis.go
index 094c3f352..cb482c1ed 100644
--- a/x/milestone/types/genesis.go
+++ b/x/milestone/types/genesis.go
@@ -23,5 +23,7 @@ func (gs GenesisState) ValidateGenesis() error {
 func DefaultParams() Params {
 	return Params{
 		MaxMilestonePropositionLength: 10,
+		FfMilestoneThreshold:          1000,
+		FfMilestoneBlockInterval:      100,
 	}
 }
diff --git a/x/milestone/types/milestone.pb.go b/x/milestone/types/milestone.pb.go
index 8c7bfb282..1ed885f16 100644
--- a/x/milestone/types/milestone.pb.go
+++ b/x/milestone/types/milestone.pb.go
@@ -165,6 +165,7 @@ func (m *MilestoneCount) GetCount() uint64 {
 type MilestoneProposition struct {
 	BlockHashes      [][]byte `protobuf:"bytes,1,rep,name=block_hashes,json=blockHashes,proto3" json:"block_hashes,omitempty"`
 	StartBlockNumber uint64   `protobuf:"varint,2,opt,name=start_block_number,json=startBlockNumber,proto3" json:"start_block_number,omitempty"`
+	ParentHash       []byte   `protobuf:"bytes,3,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"`
 }
 
 func (m *MilestoneProposition) Reset()         { *m = MilestoneProposition{} }
@@ -214,8 +215,17 @@ func (m *MilestoneProposition) GetStartBlockNumber() uint64 {
 	return 0
 }
 
+func (m *MilestoneProposition) GetParentHash() []byte {
+	if m != nil {
+		return m.ParentHash
+	}
+	return nil
+}
+
 type Params struct {
 	MaxMilestonePropositionLength uint64 `protobuf:"varint,1,opt,name=max_milestone_proposition_length,json=maxMilestonePropositionLength,proto3" json:"max_milestone_proposition_length,omitempty"`
+	FfMilestoneThreshold          uint64 `protobuf:"varint,2,opt,name=ff_milestone_threshold,json=ffMilestoneThreshold,proto3" json:"ff_milestone_threshold,omitempty"`
+	FfMilestoneBlockInterval      uint64 `protobuf:"varint,3,opt,name=ff_milestone_block_interval,json=ffMilestoneBlockInterval,proto3" json:"ff_milestone_block_interval,omitempty"`
 }
 
 func (m *Params) Reset()         { *m = Params{} }
@@ -258,6 +268,20 @@ func (m *Params) GetMaxMilestonePropositionLength() uint64 {
 	return 0
 }
 
+func (m *Params) GetFfMilestoneThreshold() uint64 {
+	if m != nil {
+		return m.FfMilestoneThreshold
+	}
+	return 0
+}
+
+func (m *Params) GetFfMilestoneBlockInterval() uint64 {
+	if m != nil {
+		return m.FfMilestoneBlockInterval
+	}
+	return 0
+}
+
 func init() {
 	proto.RegisterType((*Milestone)(nil), "heimdallv2.milestone.Milestone")
 	proto.RegisterType((*MilestoneCount)(nil), "heimdallv2.milestone.MilestoneCount")
@@ -270,37 +294,41 @@ func init() {
 }
 
 var fileDescriptor_6d06b2e981472ef5 = []byte{
-	// 479 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0x31, 0x6f, 0xd3, 0x40,
-	0x14, 0xc7, 0x7b, 0x6d, 0x12, 0x92, 0x4b, 0x84, 0xe0, 0x94, 0xc1, 0x2d, 0xaa, 0x6b, 0x05, 0x04,
-	0x11, 0x52, 0x63, 0x94, 0x0e, 0x08, 0x36, 0xd2, 0x85, 0x4a, 0xa5, 0x8a, 0x60, 0x63, 0xb1, 0xce,
-	0xf6, 0xc9, 0x3e, 0xe1, 0xbb, 0x67, 0xdd, 0x5d, 0xaa, 0x74, 0x62, 0x65, 0xe4, 0x23, 0x74, 0x64,
-	0x83, 0x81, 0x0f, 0xc1, 0x58, 0x31, 0x31, 0xa2, 0x64, 0x80, 0x8f, 0x81, 0x7c, 0x4e, 0x6c, 0x57,
-	0x62, 0xb1, 0xee, 0xde, 0xff, 0xf7, 0xee, 0xfe, 0xef, 0xaf, 0x33, 0x7e, 0x94, 0x32, 0x2e, 0x62,
-	0x9a, 0x65, 0x97, 0x53, 0x5f, 0xf0, 0x8c, 0x69, 0x03, 0x92, 0xd5, 0xab, 0x49, 0xae, 0xc0, 0x00,
-	0x19, 0xd6, 0xd4, 0xa4, 0xd2, 0x0e, 0xee, 0x53, 0xc1, 0x25, 0xf8, 0xf6, 0x5b, 0x82, 0x07, 0xfb,
-	0x11, 0x68, 0x01, 0x3a, 0xb0, 0x3b, 0xbf, 0xdc, 0x6c, 0xa4, 0x61, 0x02, 0x09, 0x94, 0xf5, 0x62,
-	0x55, 0x56, 0x47, 0x5f, 0x77, 0x71, 0xef, 0xcd, 0xf6, 0x44, 0xf2, 0x02, 0x77, 0x73, 0x05, 0x39,
-	0x68, 0xa6, 0x1c, 0xe4, 0xa1, 0x71, 0x6f, 0x76, 0xf8, 0xf3, 0xfb, 0xf1, 0x70, 0x73, 0xce, 0xab,
-	0x38, 0x56, 0x4c, 0xeb, 0x77, 0x46, 0x71, 0x99, 0x7c, 0xf9, 0xf3, 0xed, 0x29, 0x7a, 0x5b, 0xe1,
-	0xe4, 0x31, 0xee, 0x6b, 0x43, 0x95, 0x09, 0xc2, 0x0c, 0xa2, 0x0f, 0xce, 0xae, 0x87, 0xc6, 0xad,
-	0x59, 0xbb, 0xa4, 0xb0, 0x55, 0x66, 0x85, 0x40, 0x46, 0xb8, 0xc7, 0x64, 0xbc, 0xa1, 0xf6, 0x9a,
-	0x54, 0x97, 0xc9, 0xb8, 0x64, 0xf6, 0x71, 0x2b, 0xa5, 0x3a, 0x75, 0x5a, 0x1e, 0x1a, 0x0f, 0xb6,
-	0xb2, 0x2d, 0x91, 0x27, 0x78, 0x10, 0x82, 0x0a, 0xa2, 0x94, 0x72, 0x19, 0xf0, 0xd8, 0x69, 0x5b,
-	0x97, 0xdb, 0x7b, 0x42, 0x50, 0xa7, 0x85, 0x72, 0x16, 0x93, 0x31, 0x1e, 0x54, 0x49, 0x15, 0x60,
-	0xa7, 0x09, 0xf6, 0x2b, 0xe9, 0x2c, 0x26, 0x0f, 0x71, 0xcf, 0x70, 0xc1, 0xb4, 0xa1, 0x22, 0x77,
-	0xee, 0x34, 0x1d, 0xd5, 0xf5, 0x97, 0xdd, 0x4f, 0xd7, 0x47, 0xe8, 0xef, 0xf5, 0x11, 0x1a, 0x3d,
-	0xc7, 0x77, 0xab, 0xc0, 0x4e, 0x61, 0x21, 0x0d, 0x79, 0x80, 0xdb, 0x51, 0xb1, 0xb0, 0x91, 0x55,
-	0xcd, 0x65, 0xad, 0xd1, 0xf8, 0x11, 0x0f, 0xab, 0xc6, 0xb9, 0x8d, 0x8d, 0x1b, 0x0e, 0xb2, 0x70,
-	0x6a, 0xd3, 0x08, 0x8a, 0x01, 0x99, 0x76, 0x90, 0xb7, 0x57, 0x4f, 0xdd, 0xb7, 0xd2, 0x6b, 0xab,
-	0x90, 0x13, 0x4c, 0x1a, 0x19, 0x07, 0x72, 0x21, 0x42, 0xa6, 0x6e, 0x47, 0x7d, 0xaf, 0x8e, 0xfa,
-	0xc2, 0xca, 0x0d, 0x03, 0x21, 0xee, 0xcc, 0xa9, 0xa2, 0x42, 0x93, 0x0b, 0xec, 0x09, 0xba, 0x0c,
-	0xea, 0x80, 0xf2, 0xda, 0x4f, 0x90, 0x31, 0x99, 0x98, 0xf4, 0xf6, 0x30, 0x87, 0x82, 0x2e, 0xff,
-	0x67, 0xfe, 0xdc, 0xb2, 0xf5, 0x1d, 0xb3, 0xf3, 0x1f, 0x2b, 0x17, 0xdd, 0xac, 0x5c, 0xf4, 0x7b,
-	0xe5, 0xa2, 0xcf, 0x6b, 0x77, 0xe7, 0x66, 0xed, 0xee, 0xfc, 0x5a, 0xbb, 0x3b, 0xef, 0xa7, 0x09,
-	0x37, 0xe9, 0x22, 0x9c, 0x44, 0x20, 0xfc, 0x67, 0xcb, 0x39, 0x64, 0x57, 0x09, 0x48, 0x7f, 0xfb,
-	0xb0, 0x8f, 0x2f, 0xa7, 0xfe, 0xb2, 0xf1, 0x07, 0x98, 0xab, 0x9c, 0xe9, 0xb0, 0x63, 0x1f, 0xe9,
-	0xc9, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x6b, 0xcc, 0x76, 0x26, 0x03, 0x00, 0x00,
+	// 543 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x4f, 0x6f, 0xd3, 0x30,
+	0x1c, 0x9d, 0xb7, 0xb6, 0xb4, 0x6e, 0x85, 0xc0, 0xaa, 0x50, 0xb6, 0x69, 0x59, 0x55, 0x10, 0x54,
+	0x48, 0x6b, 0x50, 0x77, 0x40, 0xc0, 0x89, 0x8e, 0x03, 0x95, 0xc6, 0x54, 0x01, 0x27, 0x2e, 0x91,
+	0xd3, 0xb8, 0x89, 0x45, 0x6c, 0x47, 0xb6, 0x5b, 0x75, 0xdf, 0x80, 0x23, 0x1f, 0x61, 0x37, 0xb8,
+	0xc1, 0x81, 0x0f, 0xc1, 0x71, 0xe2, 0xc4, 0x11, 0xb5, 0x42, 0xf0, 0x31, 0x50, 0x9c, 0xe6, 0x9f,
+	0xc4, 0x25, 0xb2, 0x7f, 0xef, 0xfd, 0x5e, 0x7e, 0xef, 0x25, 0x3f, 0x78, 0x2f, 0x24, 0x94, 0xf9,
+	0x38, 0x8a, 0x96, 0x23, 0x87, 0xd1, 0x88, 0x28, 0x2d, 0x38, 0x29, 0x4e, 0xc3, 0x58, 0x0a, 0x2d,
+	0x50, 0xb7, 0x60, 0x0d, 0x73, 0xec, 0xe0, 0x36, 0x66, 0x94, 0x0b, 0xc7, 0x3c, 0x53, 0xe2, 0xc1,
+	0xfe, 0x4c, 0x28, 0x26, 0x94, 0x6b, 0x6e, 0x4e, 0x7a, 0xd9, 0x42, 0xdd, 0x40, 0x04, 0x22, 0xad,
+	0x27, 0xa7, 0xb4, 0xda, 0xff, 0xb2, 0x0b, 0x5b, 0xaf, 0x32, 0x45, 0xf4, 0x04, 0x36, 0x63, 0x29,
+	0x62, 0xa1, 0x88, 0xb4, 0x40, 0x0f, 0x0c, 0x5a, 0xe3, 0xa3, 0x1f, 0xdf, 0x4e, 0xba, 0x5b, 0x9d,
+	0xe7, 0xbe, 0x2f, 0x89, 0x52, 0x6f, 0xb4, 0xa4, 0x3c, 0xf8, 0xfc, 0xe7, 0xeb, 0x43, 0xf0, 0x3a,
+	0xa7, 0xa3, 0xfb, 0xb0, 0xad, 0x34, 0x96, 0xda, 0xf5, 0x22, 0x31, 0x7b, 0x6f, 0xed, 0xf6, 0xc0,
+	0xa0, 0x36, 0xae, 0xa7, 0x2c, 0x68, 0x90, 0x71, 0x02, 0xa0, 0x3e, 0x6c, 0x11, 0xee, 0x6f, 0x59,
+	0x7b, 0x65, 0x56, 0x93, 0x70, 0x3f, 0xe5, 0xec, 0xc3, 0x5a, 0x88, 0x55, 0x68, 0xd5, 0x7a, 0x60,
+	0xd0, 0xc9, 0x60, 0x53, 0x42, 0x0f, 0x60, 0xc7, 0x13, 0xd2, 0x9d, 0x85, 0x98, 0x72, 0x97, 0xfa,
+	0x56, 0xdd, 0x4c, 0x99, 0xbd, 0xc7, 0x13, 0xf2, 0x2c, 0x41, 0x26, 0x3e, 0x1a, 0xc0, 0x4e, 0x9e,
+	0x54, 0x42, 0x6c, 0x94, 0x89, 0xed, 0x1c, 0x9a, 0xf8, 0xe8, 0x2e, 0x6c, 0x69, 0xca, 0x88, 0xd2,
+	0x98, 0xc5, 0xd6, 0x8d, 0xf2, 0x44, 0x45, 0xfd, 0x69, 0xf3, 0xc3, 0xd5, 0x31, 0xf8, 0x7b, 0x75,
+	0x0c, 0xfa, 0x8f, 0xe1, 0xcd, 0x3c, 0xb0, 0x33, 0xb1, 0xe0, 0x1a, 0x1d, 0xc2, 0xfa, 0x2c, 0x39,
+	0x98, 0xc8, 0xf2, 0xe6, 0xb4, 0x56, 0x6a, 0xfc, 0x04, 0x60, 0x37, 0xef, 0x9c, 0x9a, 0xdc, 0xa8,
+	0xa6, 0x82, 0x27, 0xa3, 0x9a, 0x38, 0xdc, 0xc4, 0x21, 0x51, 0x16, 0xe8, 0xed, 0x15, 0xb6, 0xdb,
+	0x06, 0x7a, 0x69, 0x10, 0x74, 0x0a, 0x51, 0x29, 0x64, 0x97, 0x2f, 0x98, 0x47, 0x64, 0x35, 0xeb,
+	0x5b, 0x45, 0xd6, 0x17, 0x06, 0x4e, 0xbe, 0x4c, 0x8c, 0x25, 0xe1, 0xda, 0xe8, 0x9b, 0xcc, 0x73,
+	0x75, 0x98, 0x22, 0x89, 0x7c, 0x69, 0xd2, 0xdf, 0x00, 0x36, 0xa6, 0x58, 0x62, 0xa6, 0xd0, 0x05,
+	0xec, 0x31, 0xbc, 0x72, 0x8b, 0x28, 0xe3, 0x62, 0x70, 0x37, 0x22, 0x3c, 0xd0, 0x61, 0xd5, 0xf6,
+	0x11, 0xc3, 0xab, 0xff, 0xb9, 0x3c, 0x37, 0x5c, 0xf4, 0x0c, 0xde, 0x99, 0xcf, 0x4b, 0x72, 0x3a,
+	0x94, 0x44, 0x85, 0x22, 0xf2, 0xab, 0x2e, 0xba, 0xf3, 0x79, 0x2e, 0xf2, 0x36, 0xa3, 0xa0, 0x17,
+	0xf0, 0xb0, 0xd2, 0x9c, 0xa6, 0x40, 0xb9, 0x26, 0x72, 0x89, 0xa3, 0xea, 0xdf, 0x64, 0x95, 0x14,
+	0x4c, 0x1a, 0x93, 0x2d, 0xad, 0xf0, 0x39, 0x3e, 0xff, 0xbe, 0xb6, 0xc1, 0xf5, 0xda, 0x06, 0xbf,
+	0xd6, 0x36, 0xf8, 0xb8, 0xb1, 0x77, 0xae, 0x37, 0xf6, 0xce, 0xcf, 0x8d, 0xbd, 0xf3, 0x6e, 0x14,
+	0x50, 0x1d, 0x2e, 0xbc, 0xe1, 0x4c, 0x30, 0xe7, 0xd1, 0x6a, 0x2a, 0xa2, 0xcb, 0x40, 0x70, 0x27,
+	0xdb, 0xc2, 0x93, 0xe5, 0xc8, 0x59, 0x95, 0xd6, 0x55, 0x5f, 0xc6, 0x44, 0x79, 0x0d, 0xb3, 0x51,
+	0xa7, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x99, 0x4a, 0x88, 0xb0, 0xd3, 0x03, 0x00, 0x00,
 }
 
 func (this *Milestone) Equal(that interface{}) bool {
@@ -399,6 +427,9 @@ func (this *MilestoneProposition) Equal(that interface{}) bool {
 	if this.StartBlockNumber != that1.StartBlockNumber {
 		return false
 	}
+	if !bytes.Equal(this.ParentHash, that1.ParentHash) {
+		return false
+	}
 	return true
 }
 func (this *Params) Equal(that interface{}) bool {
@@ -423,6 +454,12 @@ func (this *Params) Equal(that interface{}) bool {
 	if this.MaxMilestonePropositionLength != that1.MaxMilestonePropositionLength {
 		return false
 	}
+	if this.FfMilestoneThreshold != that1.FfMilestoneThreshold {
+		return false
+	}
+	if this.FfMilestoneBlockInterval != that1.FfMilestoneBlockInterval {
+		return false
+	}
 	return true
 }
 func (m *Milestone) Marshal() (dAtA []byte, err error) {
@@ -539,6 +576,13 @@ func (m *MilestoneProposition) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if len(m.ParentHash) > 0 {
+		i -= len(m.ParentHash)
+		copy(dAtA[i:], m.ParentHash)
+		i = encodeVarintMilestone(dAtA, i, uint64(len(m.ParentHash)))
+		i--
+		dAtA[i] = 0x1a
+	}
 	if m.StartBlockNumber != 0 {
 		i = encodeVarintMilestone(dAtA, i, uint64(m.StartBlockNumber))
 		i--
@@ -576,6 +620,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.FfMilestoneBlockInterval != 0 {
+		i = encodeVarintMilestone(dAtA, i, uint64(m.FfMilestoneBlockInterval))
+		i--
+		dAtA[i] = 0x18
+	}
+	if m.FfMilestoneThreshold != 0 {
+		i = encodeVarintMilestone(dAtA, i, uint64(m.FfMilestoneThreshold))
+		i--
+		dAtA[i] = 0x10
+	}
 	if m.MaxMilestonePropositionLength != 0 {
 		i = encodeVarintMilestone(dAtA, i, uint64(m.MaxMilestonePropositionLength))
 		i--
@@ -656,6 +710,10 @@ func (m *MilestoneProposition) Size() (n int) {
 	if m.StartBlockNumber != 0 {
 		n += 1 + sovMilestone(uint64(m.StartBlockNumber))
 	}
+	l = len(m.ParentHash)
+	if l > 0 {
+		n += 1 + l + sovMilestone(uint64(l))
+	}
 	return n
 }
 
@@ -668,6 +726,12 @@ func (m *Params) Size() (n int) {
 	if m.MaxMilestonePropositionLength != 0 {
 		n += 1 + sovMilestone(uint64(m.MaxMilestonePropositionLength))
 	}
+	if m.FfMilestoneThreshold != 0 {
+		n += 1 + sovMilestone(uint64(m.FfMilestoneThreshold))
+	}
+	if m.FfMilestoneBlockInterval != 0 {
+		n += 1 + sovMilestone(uint64(m.FfMilestoneBlockInterval))
+	}
 	return n
 }
 
@@ -1063,6 +1127,40 @@ func (m *MilestoneProposition) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ParentHash", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowMilestone
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthMilestone
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthMilestone
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ParentHash = append(m.ParentHash[:0], dAtA[iNdEx:postIndex]...)
+			if m.ParentHash == nil {
+				m.ParentHash = []byte{}
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipMilestone(dAtA[iNdEx:])
@@ -1132,6 +1230,44 @@ func (m *Params) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field FfMilestoneThreshold", wireType)
+			}
+			m.FfMilestoneThreshold = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowMilestone
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.FfMilestoneThreshold |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field FfMilestoneBlockInterval", wireType)
+			}
+			m.FfMilestoneBlockInterval = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowMilestone
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.FfMilestoneBlockInterval |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
 		default:
 			iNdEx = preIndex
 			skippy, err := skipMilestone(dAtA[iNdEx:])
diff --git a/x/milestone/types/params.go b/x/milestone/types/params.go
index 91debbc52..218968f2b 100644
--- a/x/milestone/types/params.go
+++ b/x/milestone/types/params.go
@@ -9,5 +9,17 @@ func (p Params) Validate() error {
 	if p.MaxMilestonePropositionLength == 0 {
 		return fmt.Errorf("max milestone proposition length should not be zero")
 	}
+	if p.FfMilestoneThreshold == 0 {
+		return fmt.Errorf("ff milestone threshold should not be zero")
+	}
+	if p.FfMilestoneBlockInterval == 0 {
+		return fmt.Errorf("ff milestone block interval should not be zero")
+	}
+	if p.FfMilestoneBlockInterval >= p.FfMilestoneThreshold {
+		return fmt.Errorf("ff milestone block interval should be less than ff milestone threshold")
+	}
+	if p.FfMilestoneThreshold%p.FfMilestoneBlockInterval != 0 {
+		return fmt.Errorf("ff milestone threshold should be divisible by ff milestone block interval")
+	}
 	return nil
 }


Reviews:
user: login: lucca30
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:43:26Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:46:25Z
user: login: lucca30
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:50:59Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:52:00Z
user: login: lucca30
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:53:47Z
user: login: lucca30
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:54:03Z
user: login: lucca30
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T11:52:14Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T12:08:16Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T12:09:38Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:54:03Z
user: login: lucca30
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:56:27Z
user: login: cffls
body: A minor comment, but looks great overall.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T19:47:19Z
user: login: avalkov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T15:37:21Z
user: login: cffls
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:08:32Z

Review comments:
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
path: x/milestone/abci/abci.go
user: login: lucca30
body: To make it more readable. What about: `if startBlock >lastMilestoneBlock`
created_at: 2025-03-14T11:35:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
+			headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(lastMilestoneBlock), 1)
path: x/milestone/abci/abci.go
user: login: lucca30
body: This will not get any block unless `int64(lastMilestoneBlock) <=1`. The method expects first the `start` param and after the `end`


https://github.com/0xPolygon/heimdall-v2/blob/74329a0ca00e7b503d08a11af605648a0970da10/helper/call.go#L546-L555


Also, when I implemented this method I was assuming we were going to call it on small ranges. If this range can potentially be big, we need to improve the `GetBorChainBlocksInBatch` method. Because bor clients can limit the amount of items in batch. In general this number is big, but some RPC providers, for example, put this as 10. So we maybe need to introduce a config variable `maxBorBatch` that limits the batches calls.
created_at: 2025-03-14T11:42:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
path: x/milestone/abci/abci.go
user: login: avalkov
body: Here I am checking if we are in the case where next milestone start block will be right after last finalized milestone or it will be 1000 blocks in the future lets say.
created_at: 2025-03-14T11:46:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
path: x/milestone/abci/abci.go
user: login: lucca30
body: Oh, my bad. Yeah, now i realised the point. I don't know why I looked to this like `if startBlock-lastMilestoneBlock >= 1` or `if startBlock-lastMilestoneBlock > 0`. I see your point
created_at: 2025-03-14T11:50:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
+			headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(lastMilestoneBlock), 1)
path: x/milestone/abci/abci.go
user: login: avalkov
body: Thanks, replaced it with call to get single header.
We wont call it for big ranges, we just need to get the current execution client fork block right after the last finalized block number and check via parent relationship if they are same fork.
created_at: 2025-03-14T11:52:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,14 +295,30 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
 	result := make([][]byte, 0)
 
 	headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(startBlock), int64(startBlock+maxBlocksInProposition-1))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get headers")
 	}
 
+	if len(headers) > 0 && len(lastMilestoneHash) > 0 {
+		lastFinalizedFork := headers[0].ParentHash.Bytes()
+		if startBlock-lastMilestoneBlock > 1 {
+			headers, err := contractCaller.GetBorChainBlocksInBatch(ctx, int64(lastMilestoneBlock), 1)
path: x/milestone/abci/abci.go
user: login: lucca30
body: Nice. The best scenario possible
created_at: 2025-03-14T11:53:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,42 +28,60 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	logger := ctx.Logger()
+	propStartBlock := uint64(0)
 
-	lastMilestoneBlockNumber, err := milestoneKeeper.GetMilestoneBlockNumber(ctx)
-	if err != nil {
-		return nil, err
-	}
+	var lastMilestoneHash []byte
+	var lastMilestoneBlockNumber uint64
 
-	blocksSinceLastMilestone := reqBlock - lastMilestoneBlockNumber
+	if milestone != nil {
+		propStartBlock = milestone.EndBlock + 1
 
-	logger.Debug("blocksSinceLastMilestone", "blocksSinceLastMilestone", blocksSinceLastMilestone)
+		latestHeader, err := contractCaller.GetBorChainBlock(ctx, nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get latest header")
+		}
 
-	propStartBlock := uint64(0)
+		params, err := milestoneKeeper.GetParams(ctx)
+		if err != nil {
+			return nil, err
+		}
 
-	if milestone != nil {
-		propStartBlock = milestone.EndBlock + 1
+		if latestHeader.Number.Uint64() > milestone.EndBlock && latestHeader.Number.Uint64()-milestone.EndBlock > params.FfMilestoneThreshold {
+			propStartBlock = ((latestHeader.Number.Uint64() - milestone.EndBlock) / params.FfMilestoneBlockInterval) * params.FfMilestoneBlockInterval
path: x/milestone/abci/abci.go
user: login: lucca30
body: Even being possible understand this operation, it takes a bit of time to discover it. I'd suggest you assign it to a new variable just to make it more readable.

Something like:

```
latestHeaderMilestoneDistanceInBlocks = ((latestHeader.Number.Uint64() - milestone.EndBlock) / params.FfMilestoneBlockInterval)  * params.FfMilestoneBlockInterval
propStartBlock = milestone.EndBlock + latestHeaderMilestoneDistanceInBlocks 
```

Also, isn't:

```
propStartBlock = milestone.EndBlock + latestHeaderMilestoneDistanceInBlocks 
```
Missing a `+1`?


Here it is some values and it's output:

```
latestHeader.Number=20 | milestone.EndBlock=19 | params.FfMilestoneBlockInterval=10 | propStartBlock=19 (should be 20)
latestHeader.Number=29 | milestone.EndBlock=19 | params.FfMilestoneBlockInterval=10 | propStartBlock=29 (should be 30)
latestHeader.Number=30 | milestone.EndBlock=19 | params.FfMilestoneBlockInterval=10 | propStartBlock=29 (should be 30)
```
created_at: 2025-03-17T11:42:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -166,11 +196,39 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 		return nil, nil, "", nil
 	}
 
+	var majorityParentHash string
+	isParentHashMajority := false
+
+	for parentHash := range parentHashes {
+		key := getParentChildKey(parentHash, common.BytesToHash(blockToHash[majorityBlocks[0]]).String())
+		if parentHashToVotingPower[key] >= majorityVP {
+			isParentHashMajority = true
+			majorityParentHash = parentHash
+			break
+		}
+	}
+
+	if !isParentHashMajority {
+		logger.Debug("No parent hash found with majority support")
path: x/milestone/abci/abci.go
user: login: lucca30
body: I suppose this log you used for some validation. But still necessary on the application for future debug? 
created_at: 2025-03-17T11:44:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,19 +334,36 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([]byte, [][]byte, error) {
path: x/milestone/abci/abci.go
user: login: lucca30
body: This whole new logic of latest milestone hash check doesn't seems to fit in this function. I'd suggest either rename this function or maybe create a function specifically for this task of checking the first block of the next milestone against the latest milestone hash.
created_at: 2025-03-17T11:50:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,42 +28,60 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	logger := ctx.Logger()
+	propStartBlock := uint64(0)
 
-	lastMilestoneBlockNumber, err := milestoneKeeper.GetMilestoneBlockNumber(ctx)
-	if err != nil {
-		return nil, err
-	}
+	var lastMilestoneHash []byte
+	var lastMilestoneBlockNumber uint64
 
-	blocksSinceLastMilestone := reqBlock - lastMilestoneBlockNumber
+	if milestone != nil {
+		propStartBlock = milestone.EndBlock + 1
 
-	logger.Debug("blocksSinceLastMilestone", "blocksSinceLastMilestone", blocksSinceLastMilestone)
+		latestHeader, err := contractCaller.GetBorChainBlock(ctx, nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get latest header")
+		}
 
-	propStartBlock := uint64(0)
+		params, err := milestoneKeeper.GetParams(ctx)
+		if err != nil {
+			return nil, err
+		}
 
-	if milestone != nil {
-		propStartBlock = milestone.EndBlock + 1
+		if latestHeader.Number.Uint64() > milestone.EndBlock && latestHeader.Number.Uint64()-milestone.EndBlock > params.FfMilestoneThreshold {
+			propStartBlock = ((latestHeader.Number.Uint64() - milestone.EndBlock) / params.FfMilestoneBlockInterval) * params.FfMilestoneBlockInterval
path: x/milestone/abci/abci.go
user: login: avalkov
body: I added new variable.

Yes, I think your right, there should be +1.
created_at: 2025-03-17T12:08:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -166,11 +196,39 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 		return nil, nil, "", nil
 	}
 
+	var majorityParentHash string
+	isParentHashMajority := false
+
+	for parentHash := range parentHashes {
+		key := getParentChildKey(parentHash, common.BytesToHash(blockToHash[majorityBlocks[0]]).String())
+		if parentHashToVotingPower[key] >= majorityVP {
+			isParentHashMajority = true
+			majorityParentHash = parentHash
+			break
+		}
+	}
+
+	if !isParentHashMajority {
+		logger.Debug("No parent hash found with majority support")
path: x/milestone/abci/abci.go
user: login: avalkov
body: I didn't used it for validation, we add such logs for incase in future we have to understand why the network doesnt reach finality on milestones. I even would want them to be warning or error level but Jerry suggested just to keep them debug.
created_at: 2025-03-17T12:09:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -276,19 +334,36 @@ func GetMajorityMilestoneProposition(validatorSet *stakeTypes.ValidatorSet, extV
 	return proposition, aggregatedProposersHash, supportingValidatorList[0], nil
 }
 
-func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, contractCaller helper.IContractCaller) ([][]byte, error) {
+func getBlockHashes(ctx sdk.Context, startBlock, maxBlocksInProposition uint64, lastMilestoneHash []byte, lastMilestoneBlock uint64, contractCaller helper.IContractCaller) ([]byte, [][]byte, error) {
path: x/milestone/abci/abci.go
user: login: avalkov
body: Moved the parent-child relationship check in separate function call.
created_at: 2025-03-17T17:54:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,42 +28,64 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	logger := ctx.Logger()
+	propStartBlock := uint64(0)
 
-	lastMilestoneBlockNumber, err := milestoneKeeper.GetMilestoneBlockNumber(ctx)
-	if err != nil {
-		return nil, err
-	}
+	var lastMilestoneHash []byte
+	var lastMilestoneBlockNumber uint64
 
-	blocksSinceLastMilestone := reqBlock - lastMilestoneBlockNumber
+	if milestone != nil {
+		propStartBlock = milestone.EndBlock + 1
 
-	logger.Debug("blocksSinceLastMilestone", "blocksSinceLastMilestone", blocksSinceLastMilestone)
+		latestHeader, err := contractCaller.GetBorChainBlock(ctx, nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get latest header")
+		}
 
-	propStartBlock := uint64(0)
+		params, err := milestoneKeeper.GetParams(ctx)
+		if err != nil {
+			return nil, err
+		}
 
-	if milestone != nil {
-		propStartBlock = milestone.EndBlock + 1
+		if latestHeader.Number.Uint64() > milestone.EndBlock && latestHeader.Number.Uint64()-milestone.EndBlock > params.FfMilestoneThreshold {
+			latestHeaderMilestoneDistanceInBlocks := ((latestHeader.Number.Uint64() - milestone.EndBlock) / params.FfMilestoneBlockInterval) * params.FfMilestoneBlockInterval
+			propStartBlock = milestone.EndBlock + latestHeaderMilestoneDistanceInBlocks + 1
+		}
+
+		lastMilestoneHash = milestone.Hash
+		lastMilestoneBlockNumber = milestone.EndBlock
 	}
path: x/milestone/abci/abci.go
user: login: cffls
body: Would be nice to extract this to a function and add some unit tests for it.
created_at: 2025-03-17T18:58:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,42 +28,64 @@ func GenMilestoneProposition(ctx sdk.Context, milestoneKeeper *keeper.Keeper, co
 		return nil, err
 	}
 
-	logger := ctx.Logger()
+	propStartBlock := uint64(0)
 
-	lastMilestoneBlockNumber, err := milestoneKeeper.GetMilestoneBlockNumber(ctx)
-	if err != nil {
-		return nil, err
-	}
+	var lastMilestoneHash []byte
+	var lastMilestoneBlockNumber uint64
 
-	blocksSinceLastMilestone := reqBlock - lastMilestoneBlockNumber
+	if milestone != nil {
+		propStartBlock = milestone.EndBlock + 1
 
-	logger.Debug("blocksSinceLastMilestone", "blocksSinceLastMilestone", blocksSinceLastMilestone)
+		latestHeader, err := contractCaller.GetBorChainBlock(ctx, nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get latest header")
+		}
 
-	propStartBlock := uint64(0)
+		params, err := milestoneKeeper.GetParams(ctx)
+		if err != nil {
+			return nil, err
+		}
 
-	if milestone != nil {
-		propStartBlock = milestone.EndBlock + 1
+		if latestHeader.Number.Uint64() > milestone.EndBlock && latestHeader.Number.Uint64()-milestone.EndBlock > params.FfMilestoneThreshold {
+			latestHeaderMilestoneDistanceInBlocks := ((latestHeader.Number.Uint64() - milestone.EndBlock) / params.FfMilestoneBlockInterval) * params.FfMilestoneBlockInterval
+			propStartBlock = milestone.EndBlock + latestHeaderMilestoneDistanceInBlocks + 1
+		}
+
+		lastMilestoneHash = milestone.Hash
+		lastMilestoneBlockNumber = milestone.EndBlock
 	}
path: x/milestone/abci/abci.go
user: login: avalkov
body: Done.
created_at: 2025-03-18T15:37:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-20T06:51:32Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=0xPolygon_heimdall-v2&pullRequest=196) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=0xPolygon_heimdall-v2&pullRequest=196&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=0xPolygon_heimdall-v2&pullRequest=196&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=0xPolygon_heimdall-v2&pullRequest=196&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=0xPolygon_heimdall-v2&pullRequest=196&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=0xPolygon_heimdall-v2&pullRequest=196&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=0xPolygon_heimdall-v2&pullRequest=196)


author_association: NONE