diff --git a/.github/ISSUE_TEMPLATE/DMP_2026.yml b/.github/ISSUE_TEMPLATE/DMP_2026.yml new file mode 100644 index 000000000..9d68f7f51 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/DMP_2026.yml @@ -0,0 +1,345 @@ +name: DMP 2026 Project Template +description: List a new project for Dedicated Mentoring Program (DMP) 2026 +title: "[DMP 2026]: " +labels: ["DMP 2026"] +body: + - type: textarea + id: ticket-description + validations: + required: true + attributes: + label: Ticket Contents + value: | + ## Description + [Provide a brief description of the feature, including why it is needed and what it will accomplish.] + + - type: textarea + id: ticket-goals + validations: + required: true + attributes: + label: Goals & Mid-Point Milestone + description: List the goals of the feature. Please add the goals that must be achieved by Mid-point check-in i.e 1.5 months into the coding period. + value: | + ## Goals + - [ ] [Goal 1] + - [ ] [Goal 2] + - [ ] [Goal 3] + - [ ] [Goal 4] + - [ ] [Goals Achieved By Mid-point Milestone] + + - type: textarea + id: ticket-setup + attributes: + label: Setup/Installation + description: Please list or link setup or installation guide (if any) + + - type: textarea + id: ticket-expected-outcome + attributes: + label: Expected Outcome + description: Describe in detail what the final product or result should look like and how it should behave. + + - type: textarea + id: ticket-acceptance-criteria + attributes: + label: Acceptance Criteria + description: List the acceptance criteria for this feature. + + - type: textarea + id: ticket-implementation-details + validations: + required: true + attributes: + label: Implementation Details + description: List any technical details about the proposed implementation, including any specific technologies that will be used. + + - type: textarea + id: ticket-mockups + attributes: + label: Mockups/Wireframes + description: Include links to any visual aids, mockups, wireframes, or diagrams that help illustrate what the final product should look like. This is not always necessary, but can be very helpful in many cases. + + - type: input + id: ticket-product + attributes: + label: Product Name + placeholder: Enter Product Name + validations: + required: true + + - type: dropdown + id: ticket-organisation + attributes: + label: Organisation Name + description: Enter Organisation Name + multiple: false + options: + - Agami + - Argusoft + - ARMMAN + - Avanti Fellows + - Bandhu + - Beckn + - Belongg + - Blockster Global (CREDBEL) + - Blockster Labs / AyanWorks + - CBoard + - CHAOSS + - CHAOSS Africa + GWU + - Civis + - ConveGenius + - Consul Democracy + - COSS + - CranberryFit + - Development Gateway + - DHIS2 + - Dhiway + - Dhwani + - Digital Green + - Digital India + - Dimagi + - Drupal + - Education Initiative + - eGov + - EkShop Marketplace + - FIDE + - FinternetLabs + - Flywheel + - GovDirectory + - Haqdarshak + - Healthsites.io + - IDinsight + - If Me + - IIIT Delhi + - IIT Bombay + - IIT Delhi + - Impactyaan + - Indus Action + - Intel Health + - Key Education Foundation + - Khushi Baby + - Learning Economy + - Linux Foundation + - Mecha Systems + - Medic Mobile + - Medtronic Labs + - MetaBrainz + - Mifos + - Mojaloop + - MOSIP + - NASSCOM Foundation + - NHA + - NIUA + - Norwegian Meteorological Institute + - NSUT x SEETA x AIC + - ONDC + - ONEST + - Open Healthcare Network + - OpenCRVS + - OpenFn + - OpenIMIS + - OpenMRS + - OpenSPP + - Piramal Swasthya + - Planet Read + - Policy Engine + - Pratham Books + - Project Second Chance + - Project Tech4Dev + - Protean + - RCTS-IIITH + - Reap Benefit + - Resolve to Save Lives + - Rocket Learning + - Rumsan + - Sahamati + - SamagraX + - Samanvay Foundation + - Sampatti Card + - Sanketika + - ShikshaLokam + - SimPPL + - Sugar Labs + - Swasth Alliance + - Swecha + - Tarento + - Tattle + - Tech4Dev + - Tekdi + - The Apprentice Project + - The Mifos Initiative + - Thoughtworks + - Tibil + - TinkerHub + - Trustin + - Tuner Labs + - TYCIA + - UNICEF + - United Nations + - Ushahidi + - Win Over Cancer + - WRI + - Zendalona + - Zenysis + - Arghyam + validations: + required: true + + - type: dropdown + id: ticket-governance-domain + attributes: + label: Domain + options: + - ⁠Healthcare + - ⁠Education + - Financial Inclusion + - ⁠Livelihoods + - ⁠Skilling + - ⁠Learning & Development + - ⁠Agriculture + - ⁠Service Delivery + - Open Source Library + - Water + validations: + required: true + + + - type: dropdown + id: ticket-technical-skills-required + attributes: + label: Tech Skills Needed + description: Select the technologies needed for this ticket (use Ctrl or Command to select multiple) + multiple: true + options: + - .NET + - Angular + - Artificial Intelligence + - ASP.NET + - AWS + - Babel + - Bootstrap + - C# + - Chart.js + - CI/CD + - Computer Vision + - CORS + - cURL + - Cypress + - D3.js + - Database + - Debugging + - Design + - DevOps + - Django + - Docker + - Electron + - ESLint + - Express.js + - Feature + - Flask + - Go + - GraphQL + - HTML + - Ionic + - Jest + - Java + - JavaScript + - Jenkins + - JWT + - Kubernetes + - Laravel + - Machine Learning + - Maintenance + - Markdown + - Material-UI + - Microservices + - MongoDB + - Mobile + - Mockups + - Mocha + - Natural Language Processing + - NestJS + - Node.js + - NUnit + - OAuth + - Performance Improvement + - Prettier + - Python + - Question + - React + - React Native + - Redux + - RESTful APIs + - Ruby + - Ruby on Rails + - Rust + - Scala + - Security + - Selenium + - SEO + - Serverless + - Solidity + - Spring Boot + - SQL + - Swagger + - Tailwind CSS + - Test + - Testing Library + - Three.js + - TypeScript + - UI/UX/Design + - Virtual Reality + - Vue.js + - WebSockets + - Webpack + - Other + validations: + required: true + + - type: textarea + id: ticket-mentors + attributes: + label: Mentor(s) + description: Please tag relevant mentors for the ticket + validations: + required: true + + - type: dropdown + id: ticket-category + attributes: + label: Category + description: Choose the categories that best describe your ticket + multiple: true + options: + - API + - Analytics + - Accessibility + - Backend + - Breaking Change + - Beginner Friendly + - Configuration + - CI/CD + - Database + - Data Science + - Deprecation + - Documentation + - Delpoyment + - Frontend + - Internationalization + - Localization + - Machine Learning + - Maintenance + - Mobile + - Performance Improvement + - Question + - Refactoring + - Research + - Needs Reproduction + - SEO + - Security + - Testing + - AI + - Other + validations: + required: true diff --git a/libp2p/peer/persistent/async_/peerstore.py b/libp2p/peer/persistent/async_/peerstore.py index 7a28f06ed..1849e23d1 100644 --- a/libp2p/peer/persistent/async_/peerstore.py +++ b/libp2p/peer/persistent/async_/peerstore.py @@ -17,6 +17,10 @@ from libp2p.abc_async import IAsyncPeerStore from libp2p.crypto.keys import KeyPair, PrivateKey, PublicKey +from libp2p.crypto.serialization import ( + deserialize_private_key, + deserialize_public_key, +) from libp2p.custom_types import MetadataValue from libp2p.peer.envelope import Envelope from libp2p.peer.id import ID @@ -147,16 +151,15 @@ async def _load_peer_data(self, peer_id: ID) -> PeerData: key_key = self._get_key_key(peer_id) key_data = await self.datastore.get(key_key) if key_data: - # For now, store keys as metadata until keypair serialization - # keys_metadata = deserialize_metadata(key_data) - # TODO: Implement proper keypair deserialization - # peer_data.pubkey = deserialize_public_key( - # keys_metadata.get(b"pubkey", b"") - # ) - # peer_data.privkey = deserialize_private_key( - # keys_metadata.get(b"privkey", b"") - # ) - pass + keys_metadata = deserialize_metadata(key_data) + if keys_metadata.get("pubkey"): + peer_data.pubkey = deserialize_public_key( + keys_metadata["pubkey"] + ) + if keys_metadata.get("privkey"): + peer_data.privkey = deserialize_private_key( + keys_metadata["privkey"] + ) # Load metadata metadata_key = self._get_metadata_key(peer_id) @@ -212,7 +215,7 @@ async def _save_peer_data(self, peer_id: ID, peer_data: PeerData) -> None: addr_data = serialize_addresses(peer_data.addrs) await self.datastore.put(addr_key, addr_data) - # Save keys (temporarily as metadata until proper keypair serialization) + # Save keys as serialized metadata if peer_data.pubkey or peer_data.privkey: key_key = self._get_key_key(peer_id) keys_metadata = {} diff --git a/libp2p/peer/persistent/sync/peerstore.py b/libp2p/peer/persistent/sync/peerstore.py index 3cc3c7930..26796798a 100644 --- a/libp2p/peer/persistent/sync/peerstore.py +++ b/libp2p/peer/persistent/sync/peerstore.py @@ -16,6 +16,10 @@ from libp2p.abc import IPeerStore from libp2p.crypto.keys import KeyPair, PrivateKey, PublicKey +from libp2p.crypto.serialization import ( + deserialize_private_key, + deserialize_public_key, +) from libp2p.custom_types import MetadataValue from libp2p.peer.envelope import Envelope from libp2p.peer.id import ID @@ -149,16 +153,15 @@ def _load_peer_data(self, peer_id: ID) -> PeerData: key_key = self._get_key_key(peer_id) key_data = self.datastore.get(key_key) if key_data: - # For now, store keys as metadata until keypair serialization - # keys_metadata = deserialize_metadata(key_data) - # TODO: Implement proper keypair deserialization - # peer_data.pubkey = deserialize_public_key( - # keys_metadata.get(b"pubkey", b"") - # ) - # peer_data.privkey = deserialize_private_key( - # keys_metadata.get(b"privkey", b"") - # ) - pass + keys_metadata = deserialize_metadata(key_data) + if keys_metadata.get("pubkey"): + peer_data.pubkey = deserialize_public_key( + keys_metadata["pubkey"] + ) + if keys_metadata.get("privkey"): + peer_data.privkey = deserialize_private_key( + keys_metadata["privkey"] + ) # Load metadata metadata_key = self._get_metadata_key(peer_id) @@ -214,7 +217,7 @@ def _save_peer_data(self, peer_id: ID, peer_data: PeerData) -> None: addr_data = serialize_addresses(peer_data.addrs) self.datastore.put(addr_key, addr_data) - # Save keys (temporarily as metadata until proper keypair serialization) + # Save keys as serialized metadata if peer_data.pubkey or peer_data.privkey: key_key = self._get_key_key(peer_id) keys_metadata = {} diff --git a/tests/core/peer/test_persistent_peerstore_persistence.py b/tests/core/peer/test_persistent_peerstore_persistence.py index 374cb8722..9b9d3d5a3 100644 --- a/tests/core/peer/test_persistent_peerstore_persistence.py +++ b/tests/core/peer/test_persistent_peerstore_persistence.py @@ -11,6 +11,7 @@ import pytest from multiaddr import Multiaddr +from libp2p.crypto.ed25519 import create_new_key_pair from libp2p.peer.id import ID from libp2p.peer.peerstore import PeerStoreError from libp2p.peer.persistent import ( @@ -471,3 +472,60 @@ def test_sync_cross_backend_no_persistence(): addrs = sqlite_store.addrs(peer_id) assert len(addrs) == 0 sqlite_store.close() + + +# ============================================================================ +# Keypair Persistence Tests +# ============================================================================ + + +def test_sync_sqlite_keypair_persistence(): + """Test that keypairs survive a peerstore restart.""" + key_pair = create_new_key_pair() + peer_id = ID.from_pubkey(key_pair.public_key) + + with tempfile.TemporaryDirectory() as temp_dir: + db_path = Path(temp_dir) / "test.db" + + peerstore1 = create_sync_sqlite_peerstore(str(db_path)) + peerstore1.add_key_pair(peer_id, key_pair) + peerstore1.close() + + peerstore2 = create_sync_sqlite_peerstore(str(db_path)) + assert peerstore2.pubkey(peer_id) == key_pair.public_key + assert peerstore2.privkey(peer_id) == key_pair.private_key + peerstore2.close() + + +def test_sync_memory_keypair_not_persistent(): + """Test that keypairs are not persisted in the memory backend.""" + key_pair = create_new_key_pair() + peer_id = ID.from_pubkey(key_pair.public_key) + + peerstore1 = create_sync_memory_peerstore() + peerstore1.add_key_pair(peer_id, key_pair) + peerstore1.close() + + peerstore2 = create_sync_memory_peerstore() + with pytest.raises(PeerStoreError): + peerstore2.pubkey(peer_id) + peerstore2.close() + + +@pytest.mark.trio +async def test_async_sqlite_keypair_persistence(): + """Test that keypairs survive a peerstore restart (async).""" + key_pair = create_new_key_pair() + peer_id = ID.from_pubkey(key_pair.public_key) + + with tempfile.TemporaryDirectory() as temp_dir: + db_path = Path(temp_dir) / "test.db" + + peerstore1 = create_async_sqlite_peerstore(str(db_path)) + await peerstore1.add_key_pair_async(peer_id, key_pair) + await peerstore1.close_async() + + peerstore2 = create_async_sqlite_peerstore(str(db_path)) + assert await peerstore2.pubkey_async(peer_id) == key_pair.public_key + assert await peerstore2.privkey_async(peer_id) == key_pair.private_key + await peerstore2.close_async()