diff --git a/dot/parachain/prospective-parachains/prospective-parachains.go b/dot/parachain/prospective-parachains/prospective-parachains.go
index 489d80f3ef..13c0a396c3 100644
--- a/dot/parachain/prospective-parachains/prospective-parachains.go
+++ b/dot/parachain/prospective-parachains/prospective-parachains.go
@@ -72,7 +72,8 @@ func (pp *ProspectiveParachains) processMessage(msg any) {
 	case GetHypotheticalMembership:
 		panic("not implemented yet: see issue #4311")
 	case GetMinimumRelayParents:
-		panic("not implemented yet: see issue #4312")
+		// Directly use the msg since it's already of type GetMinimumRelayParents
+		pp.getMinimumRelayParents(msg.RelayChainBlockHash, msg.Sender)
 	case GetProspectiveValidationData:
 		panic("not implemented yet: see issue #4313")
 	default:
@@ -92,6 +93,30 @@ func (*ProspectiveParachains) ProcessBlockFinalizedSignal(parachaintypes.BlockFi
 	return nil
 }
 
+func (pp *ProspectiveParachains) getMinimumRelayParents(
+	relayChainBlockHash common.Hash,
+	sender chan []ParaIDBlockNumber,
+) {
+	var result []ParaIDBlockNumber
+
+	// Check if the relayChainBlockHash exists in active_leaves
+	if exists := pp.View.activeLeaves[relayChainBlockHash]; exists {
+		// Retrieve data associated with the relayChainBlockHash
+		if leafData, found := pp.View.perRelayParent[relayChainBlockHash]; found {
+			// Iterate over fragment_chains and collect the data
+			for paraID, fragmentChain := range leafData.fragmentChains {
+				result = append(result, ParaIDBlockNumber{
+					ParaId:      paraID,
+					BlockNumber: fragmentChain.scope.relayParent.Number,
+				})
+			}
+		}
+	}
+
+	// Send the result through the sender channel
+	sender <- result
+}
+
 func (pp *ProspectiveParachains) getBackableCandidates(
 	msg GetBackableCandidates,
 ) {
diff --git a/dot/parachain/prospective-parachains/prospective_parachains_test.go b/dot/parachain/prospective-parachains/prospective_parachains_test.go
index b9d2d3b2c5..8b5b8bb7d0 100644
--- a/dot/parachain/prospective-parachains/prospective_parachains_test.go
+++ b/dot/parachain/prospective-parachains/prospective_parachains_test.go
@@ -72,7 +72,7 @@ func makeCandidate(
 	commitmentsHash := commitments.Hash()
 
 	candidate := dummyCandidateReceiptBadSig(relayParent, &commitmentsHash)
-	candidate.CommitmentsHash = commitmentsHash
+	candidate.CommitmentsHash = commitments.Hash()
 	candidate.Descriptor.ParaID = paraID
 
 	pvdh, err := pvd.Hash()
@@ -92,6 +92,111 @@ func makeCandidate(
 	return result
 }
 
+func padTo32Bytes(input []byte) common.Hash {
+	var hash common.Hash
+	copy(hash[:], input)
+	return hash
+}
+
+// TestGetMinimumRelayParents ensures that getMinimumRelayParents
+// processes the relay parent hash and correctly sends the output via the channel
+func TestGetMinimumRelayParents(t *testing.T) {
+	// Setup a mock View with active leaves and relay parent data
+
+	mockRelayParent := relayChainBlockInfo{
+		Hash:   padTo32Bytes([]byte("active_hash")),
+		Number: 10,
+	}
+
+	ancestors := []relayChainBlockInfo{
+		{
+			Hash:   padTo32Bytes([]byte("active_hash_7")),
+			Number: 9,
+		},
+		{
+			Hash:   padTo32Bytes([]byte("active_hash_8")),
+			Number: 8,
+		},
+		{
+			Hash:   padTo32Bytes([]byte("active_hash_9")),
+			Number: 7,
+		},
+	}
+
+	baseConstraints := &parachaintypes.Constraints{
+		MinRelayParentNumber: 5,
+	}
+
+	mockScope, err := newScopeWithAncestors(mockRelayParent, baseConstraints, nil, 10, ancestors)
+	assert.NoError(t, err)
+
+	mockScope2, err := newScopeWithAncestors(mockRelayParent, baseConstraints, nil, 10, nil)
+	assert.NoError(t, err)
+
+	mockView := &view{
+		activeLeaves: map[common.Hash]bool{
+			common.BytesToHash([]byte("active_hash")): true,
+		},
+		perRelayParent: map[common.Hash]*relayParentData{
+			common.BytesToHash([]byte("active_hash")): {
+				fragmentChains: map[parachaintypes.ParaID]*fragmentChain{
+					parachaintypes.ParaID(1): newFragmentChain(mockScope, newCandidateStorage()),
+					parachaintypes.ParaID(2): newFragmentChain(mockScope2, newCandidateStorage()),
+				},
+			},
+		},
+	}
+
+	// Initialize ProspectiveParachains with the mock view
+	pp := &ProspectiveParachains{
+		View: mockView,
+	}
+
+	// Create a channel to capture the output
+	sender := make(chan []ParaIDBlockNumber, 1)
+
+	// Execute the method under test
+	pp.getMinimumRelayParents(common.BytesToHash([]byte("active_hash")), sender)
+
+	expected := []ParaIDBlockNumber{
+		{
+			ParaId:      1,
+			BlockNumber: 10,
+		},
+		{
+			ParaId:      2,
+			BlockNumber: 10,
+		},
+	}
+	// Validate the results
+	result := <-sender
+	assert.Len(t, result, 2)
+	assert.Equal(t, expected, result)
+}
+
+// TestGetMinimumRelayParents_NoActiveLeaves ensures that getMinimumRelayParents
+// correctly handles the case where there are no active leaves.
+func TestGetMinimumRelayParents_NoActiveLeaves(t *testing.T) {
+	mockView := &view{
+		activeLeaves:   map[common.Hash]bool{},
+		perRelayParent: map[common.Hash]*relayParentData{},
+	}
+
+	// Initialize ProspectiveParachains with the mock view
+	pp := &ProspectiveParachains{
+		View: mockView,
+	}
+
+	// Create a channel to capture the output
+	sender := make(chan []ParaIDBlockNumber, 1)
+
+	// Execute the method under test
+	pp.getMinimumRelayParents(common.BytesToHash([]byte("active_hash")), sender)
+	// Validate the results
+	result := <-sender
+	assert.Empty(t, result, "Expected result to be empty when no active leaves are present")
+}
+
 func TestGetBackableCandidates(t *testing.T) {
 	candidateRelayParent1 := common.Hash{0x01}
 	candidateRelayParent2 := common.Hash{0x02}