Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Rename `ProjId` -> `Id`
Alexis Sellier committed 3 years ago
commit 927f062dc9cb1d68050f828d953b057f16325f86
parent c80ba01ed92d9217e4df85643f498acebcc9a908
16 files changed +146 -139
modified node/src/client/handle.rs
@@ -4,7 +4,7 @@ use crossbeam_channel as chan;
use nakamoto_net::Waker;
use thiserror::Error;

-
use crate::identity::ProjId;
+
use crate::identity::Id;
use crate::protocol;
use crate::protocol::{CommandError, FetchLookup};

@@ -55,28 +55,28 @@ pub struct Handle<W: Waker> {

impl<W: Waker> traits::Handle for Handle<W> {
    /// Retrieve or update the given project from the network.
-
    fn fetch(&self, id: ProjId) -> Result<FetchLookup, Error> {
+
    fn fetch(&self, id: Id) -> Result<FetchLookup, Error> {
        let (sender, receiver) = chan::bounded(1);
        self.commands.send(protocol::Command::Fetch(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Start tracking the given project. Doesn't do anything if the project is already tracked.
-
    fn track(&self, id: ProjId) -> Result<bool, Error> {
+
    fn track(&self, id: Id) -> Result<bool, Error> {
        let (sender, receiver) = chan::bounded(1);
        self.commands.send(protocol::Command::Track(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Untrack the given project and delete it from storage.
-
    fn untrack(&self, id: ProjId) -> Result<bool, Error> {
+
    fn untrack(&self, id: Id) -> Result<bool, Error> {
        let (sender, receiver) = chan::bounded(1);
        self.commands.send(protocol::Command::Untrack(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Notify the client that a project has been updated.
-
    fn updated(&self, id: ProjId) -> Result<(), Error> {
+
    fn updated(&self, id: Id) -> Result<(), Error> {
        self.command(protocol::Command::AnnounceRefsUpdate(id))
    }

@@ -102,14 +102,14 @@ pub mod traits {

    pub trait Handle {
        /// Retrieve or update the project from network.
-
        fn fetch(&self, id: ProjId) -> Result<FetchLookup, Error>;
+
        fn fetch(&self, id: Id) -> Result<FetchLookup, Error>;
        /// Start tracking the given project. Doesn't do anything if the project is already
        /// tracked.
-
        fn track(&self, id: ProjId) -> Result<bool, Error>;
+
        fn track(&self, id: Id) -> Result<bool, Error>;
        /// Untrack the given project and delete it from storage.
-
        fn untrack(&self, id: ProjId) -> Result<bool, Error>;
+
        fn untrack(&self, id: Id) -> Result<bool, Error>;
        /// Notify the client that a project has been updated.
-
        fn updated(&self, id: ProjId) -> Result<(), Error>;
+
        fn updated(&self, id: Id) -> Result<(), Error>;
        /// Send a command to the command channel, and wake up the event loop.
        fn command(&self, cmd: protocol::Command) -> Result<(), Error>;
        /// Ask the client to shutdown.
modified node/src/control.rs
@@ -9,7 +9,7 @@ use std::{fs, io, net};

use crate::client;
use crate::client::handle::traits::Handle;
-
use crate::identity::ProjId;
+
use crate::identity::Id;
use crate::protocol::FetchLookup;
use crate::protocol::FetchResult;

@@ -110,7 +110,7 @@ fn drain<H: Handle>(stream: &UnixStream, handle: &H) -> Result<(), DrainError> {
    Ok(())
}

-
fn fetch<W: Write, H: Handle>(id: ProjId, mut writer: W, handle: &H) -> Result<(), DrainError> {
+
fn fetch<W: Write, H: Handle>(id: Id, mut writer: W, handle: &H) -> Result<(), DrainError> {
    match handle.fetch(id.clone()) {
        Err(e) => {
            return Err(DrainError::Client(e));
@@ -161,7 +161,7 @@ mod tests {
    use std::{net, thread};

    use super::*;
-
    use crate::identity::ProjId;
+
    use crate::identity::Id;
    use crate::test;

    #[test]
@@ -169,7 +169,7 @@ mod tests {
        let tmp = tempfile::tempdir().unwrap();
        let handle = test::handle::Handle::default();
        let socket = tmp.path().join("alice.sock");
-
        let projs = test::arbitrary::set::<ProjId>(1..3);
+
        let projs = test::arbitrary::set::<Id>(1..3);

        thread::spawn({
            let socket = socket.clone();
modified node/src/git.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
use git_ref_format as format;

use crate::collections::HashMap;
-
use crate::identity::UserId;
+
use crate::identity::PublicKey;
use crate::storage::refs::Refs;
use crate::storage::RemoteId;

@@ -43,7 +43,7 @@ pub fn remote_refs(url: &Url) -> Result<HashMap<RemoteId, Refs>, ListRefsError>

    let refs = remote.list()?;
    for r in refs {
-
        let (id, refname) = parse_ref::<UserId>(r.name())?;
+
        let (id, refname) = parse_ref::<PublicKey>(r.name())?;
        let entry = remotes.entry(id).or_insert_with(Refs::default);

        entry.insert(refname, r.oid().into());
modified node/src/identity.rs
@@ -12,48 +12,47 @@ use crate::hash;
use crate::serde_ext;
use crate::storage::Remotes;

-
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));
+
pub use crypto::PublicKey;

-
/// A user's identifier is simply their public key.
-
pub type UserId = crypto::PublicKey;
+
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));

#[derive(Error, Debug)]
-
pub enum ProjIdError {
+
pub enum IdError {
    #[error("invalid digest: {0}")]
    InvalidDigest(#[from] hash::DecodeError),
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-
pub struct ProjId(hash::Digest);
+
pub struct Id(hash::Digest);

-
impl fmt::Display for ProjId {
+
impl fmt::Display for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.encode())
    }
}

-
impl fmt::Debug for ProjId {
+
impl fmt::Debug for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ProjId({})", self.encode())
    }
}

-
impl ProjId {
+
impl Id {
    pub fn encode(&self) -> String {
        multibase::encode(multibase::Base::Base58Btc, &self.0.as_ref())
    }
}

-
impl FromStr for ProjId {
-
    type Err = ProjIdError;
+
impl FromStr for Id {
+
    type Err = IdError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(hash::Digest::from_str(s)?))
    }
}

-
impl TryFrom<OsString> for ProjId {
-
    type Error = ProjIdError;
+
impl TryFrom<OsString> for Id {
+
    type Error = IdError;

    fn try_from(value: OsString) -> Result<Self, Self::Error> {
        let string = value.to_string_lossy();
@@ -61,13 +60,13 @@ impl TryFrom<OsString> for ProjId {
    }
}

-
impl From<hash::Digest> for ProjId {
+
impl From<hash::Digest> for Id {
    fn from(digest: hash::Digest) -> Self {
        Self(digest)
    }
}

-
impl serde::Serialize for ProjId {
+
impl serde::Serialize for Id {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
@@ -76,7 +75,7 @@ impl serde::Serialize for ProjId {
    }
}

-
impl<'de> serde::Deserialize<'de> for ProjId {
+
impl<'de> serde::Deserialize<'de> for Id {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
@@ -143,7 +142,7 @@ impl fmt::Display for Did {
#[derive(Debug, Clone)]
pub struct Project {
    /// The project identifier.
-
    pub id: ProjId,
+
    pub id: Id,
    /// The latest project identity document.
    pub doc: Doc,
    /// The project remotes.
@@ -177,7 +176,7 @@ pub struct Doc {
}

impl Doc {
-
    pub fn write<W: io::Write>(&self, mut writer: W) -> Result<ProjId, DocError> {
+
    pub fn write<W: io::Write>(&self, mut writer: W) -> Result<Id, DocError> {
        let mut buf = Vec::new();
        let mut ser =
            serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
@@ -185,7 +184,7 @@ impl Doc {
        self.serialize(&mut ser)?;

        let digest = hash::Digest::new(&buf);
-
        let id = ProjId::from(digest);
+
        let id = Id::from(digest);

        // TODO: Currently, we serialize the document in canonical JSON. This isn't strictly
        // necessary, as we could use CJSON just to get the hash, but then write a prettified
@@ -203,11 +202,12 @@ impl Doc {
#[cfg(test)]
mod test {
    use super::*;
+
    use crate::crypto::PublicKey;
    use quickcheck_macros::quickcheck;
    use std::collections::HashSet;

    #[quickcheck]
-
    fn prop_user_id_equality(a: UserId, b: UserId) {
+
    fn prop_key_equality(a: PublicKey, b: PublicKey) {
        assert_ne!(a, b);

        let mut hm = HashSet::new();
@@ -219,17 +219,17 @@ mod test {
    }

    #[quickcheck]
-
    fn prop_encode_decode(input: UserId) {
+
    fn prop_encode_decode(input: PublicKey) {
        let encoded = input.to_string();
-
        let decoded = UserId::from_str(&encoded).unwrap();
+
        let decoded = PublicKey::from_str(&encoded).unwrap();

        assert_eq!(input, decoded);
    }

    #[quickcheck]
-
    fn prop_json_eq_str(user: UserId, proj: ProjId, did: Did) {
-
        let json = serde_json::to_string(&user).unwrap();
-
        assert_eq!(format!("\"{}\"", user), json);
+
    fn prop_json_eq_str(pk: PublicKey, proj: Id, did: Did) {
+
        let json = serde_json::to_string(&pk).unwrap();
+
        assert_eq!(format!("\"{}\"", pk), json);

        let json = serde_json::to_string(&proj).unwrap();
        assert_eq!(format!("\"{}\"", proj), json);
modified node/src/protocol.rs
@@ -21,7 +21,7 @@ use crate::address_manager::AddressManager;
use crate::clock::RefClock;
use crate::collections::{HashMap, HashSet};
use crate::crypto;
-
use crate::identity::{ProjId, Project, UserId};
+
use crate::identity::{Id, Project, PublicKey};
use crate::protocol::config::ProjectTracking;
use crate::protocol::message::Message;
use crate::protocol::peer::{Peer, PeerError, PeerState};
@@ -43,7 +43,7 @@ pub const MAX_TIME_DELTA: LocalDuration = LocalDuration::from_mins(60);
/// Network node identifier.
pub type NodeId = crypto::PublicKey;
/// Network routing table. Keeps track of where projects are hosted.
-
pub type Routing = HashMap<ProjId, HashSet<NodeId>>;
+
pub type Routing = HashMap<Id, HashSet<NodeId>>;
/// Seconds since epoch.
pub type Timestamp = u64;

@@ -91,11 +91,11 @@ pub enum FetchResult {
/// Commands sent to the protocol by the operator.
#[derive(Debug)]
pub enum Command {
-
    AnnounceRefsUpdate(ProjId),
+
    AnnounceRefsUpdate(Id),
    Connect(net::SocketAddr),
-
    Fetch(ProjId, chan::Sender<FetchLookup>),
-
    Track(ProjId, chan::Sender<bool>),
-
    Untrack(ProjId, chan::Sender<bool>),
+
    Fetch(Id, chan::Sender<FetchLookup>),
+
    Track(Id, chan::Sender<bool>),
+
    Untrack(Id, chan::Sender<bool>),
}

/// Command-related errors.
@@ -151,8 +151,8 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco
        }
    }

-
    pub fn seeds(&self, proj: &ProjId) -> Box<dyn Iterator<Item = (&NodeId, &Peer)> + '_> {
-
        if let Some(peers) = self.routing.get(proj) {
+
    pub fn seeds(&self, id: &Id) -> Box<dyn Iterator<Item = (&NodeId, &Peer)> + '_> {
+
        if let Some(peers) = self.routing.get(id) {
            Box::new(
                peers
                    .iter()
@@ -163,7 +163,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco
        }
    }

-
    pub fn tracked(&self) -> Result<Vec<ProjId>, storage::Error> {
+
    pub fn tracked(&self) -> Result<Vec<Id>, storage::Error> {
        let tracked = match &self.config.project_tracking {
            ProjectTracking::All { blocked } => self
                .storage
@@ -180,8 +180,8 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco

    /// Track a project.
    /// Returns whether or not the tracking policy was updated.
-
    pub fn track(&mut self, proj: ProjId) -> bool {
-
        self.out_of_sync = self.config.track(proj);
+
    pub fn track(&mut self, id: Id) -> bool {
+
        self.out_of_sync = self.config.track(id);
        self.out_of_sync
    }

@@ -189,8 +189,8 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco
    /// Returns whether or not the tracking policy was updated.
    /// Note that when untracking, we don't announce anything to the network. This is because by
    /// simply not announcing it anymore, it will eventually be pruned by nodes.
-
    pub fn untrack(&mut self, proj: ProjId) -> bool {
-
        self.config.untrack(proj)
+
    pub fn untrack(&mut self, id: Id) -> bool {
+
        self.config.untrack(id)
    }

    /// Find the closest `n` peers by proximity in tracking graphs.
@@ -236,13 +236,13 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco
        &mut self.context.io
    }

-
    pub fn lookup(&self, proj: &ProjId) -> Lookup {
+
    pub fn lookup(&self, id: &Id) -> Lookup {
        Lookup {
-
            local: self.context.storage.get(proj).unwrap(),
+
            local: self.context.storage.get(id).unwrap(),
            remote: self
                .context
                .routing
-
                .get(proj)
+
                .get(id)
                .map_or(vec![], |r| r.iter().cloned().collect()),
        }
    }
@@ -263,13 +263,13 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protoco

    fn get_inventories(&mut self) -> Result<(), storage::Error> {
        let mut msgs = Vec::new();
-
        for proj in self.tracked()? {
-
            for (_, peer) in self.seeds(&proj) {
+
        for id in self.tracked()? {
+
            for (_, peer) in self.seeds(&id) {
                if peer.is_negotiated() {
                    msgs.push((
                        peer.addr,
                        Message::GetInventory {
-
                            ids: vec![proj.clone()],
+
                            ids: vec![id.clone()],
                        },
                    ));
                }
@@ -366,27 +366,27 @@ where

        match cmd {
            Command::Connect(addr) => self.context.connect(addr),
-
            Command::Fetch(proj, resp) => {
-
                if !self.config.is_tracking(&proj) {
+
            Command::Fetch(id, resp) => {
+
                if !self.config.is_tracking(&id) {
                    resp.send(FetchLookup::NotTracking).ok();
                    return;
                }

-
                let seeds = self.seeds(&proj).collect::<Vec<_>>();
+
                let seeds = self.seeds(&id).collect::<Vec<_>>();
                let seeds = if let Some(seeds) = NonEmpty::from_vec(seeds) {
                    seeds
                } else {
-
                    log::error!("No seeds found for {}", proj);
+
                    log::error!("No seeds found for {}", id);
                    resp.send(FetchLookup::NotFound).ok();

                    return;
                };
-
                log::debug!("Found {} seeds for {}", seeds.len(), proj);
+
                log::debug!("Found {} seeds for {}", seeds.len(), id);

-
                let mut repo = match self.storage.repository(&proj) {
+
                let mut repo = match self.storage.repository(&id) {
                    Ok(repo) => repo,
                    Err(err) => {
-
                        log::error!("Error opening repo for {}: {}", proj, err);
+
                        log::error!("Error opening repo for {}: {}", id, err);
                        resp.send(FetchLookup::Error(err.into())).ok();

                        return;
@@ -407,7 +407,7 @@ where
                        host: Some(peer.addr.ip().to_string()),
                        port: Some(peer.addr.port()),
                        // TODO: Fix upstream crate so that it adds a `/` when needed.
-
                        path: format!("/{}", proj).into(),
+
                        path: format!("/{}", id).into(),
                        ..Url::default()
                    }) {
                        Ok(()) => {
@@ -424,21 +424,21 @@ where
                    }
                }
            }
-
            Command::Track(proj, resp) => {
-
                resp.send(self.track(proj)).ok();
+
            Command::Track(id, resp) => {
+
                resp.send(self.track(id)).ok();
            }
-
            Command::Untrack(proj, resp) => {
-
                resp.send(self.untrack(proj)).ok();
+
            Command::Untrack(id, resp) => {
+
                resp.send(self.untrack(id)).ok();
            }
-
            Command::AnnounceRefsUpdate(proj) => {
-
                let user = *self.storage.public_key();
-
                let repo = self.storage.repository(&proj).unwrap();
-
                let remote = repo.remote(&user).unwrap();
+
            Command::AnnounceRefsUpdate(id) => {
+
                let signer = *self.storage.public_key();
+
                let repo = self.storage.repository(&id).unwrap();
+
                let remote = repo.remote(&signer).unwrap();
                let peers = self.peers.negotiated().map(|(_, p)| p.addr);
                let refs = remote.refs.unverified();

                self.context
-
                    .broadcast(Message::RefsUpdate { proj, user, refs }, peers);
+
                    .broadcast(Message::RefsUpdate { id, signer, refs }, peers);
            }
        }
    }
@@ -712,16 +712,16 @@ where
    }

    /// Process a peer inventory update announcement by (maybe) fetching.
-
    fn process_refs_update(&mut self, proj: &ProjId, _user: &UserId, remote: &Url) -> bool {
+
    fn process_refs_update(&mut self, id: &Id, _user: &PublicKey, remote: &Url) -> bool {
        // TODO: Check that we're tracking this user as well.
-
        if self.config.is_tracking(proj) {
-
            self.fetch(proj, remote);
+
        if self.config.is_tracking(id) {
+
            self.fetch(id, remote);
        }
        // TODO: If refs were updated, return `true`.
        false
    }

-
    fn fetch(&mut self, proj_id: &ProjId, remote: &Url) {
+
    fn fetch(&mut self, proj_id: &Id, remote: &Url) {
        // TODO: Verify refs before adding them to storage.
        let mut repo = self.storage.repository(proj_id).unwrap();
        repo.fetch(&Url {
modified node/src/protocol/config.rs
@@ -3,7 +3,7 @@ use std::net;
use git_url::Url;

use crate::collections::HashSet;
-
use crate::identity::{ProjId, UserId};
+
use crate::identity::{Id, PublicKey};
use crate::protocol::message::{Address, Envelope, Message};

/// Peer-to-peer network.
@@ -34,9 +34,9 @@ impl Network {
#[derive(Debug, Clone)]
pub enum ProjectTracking {
    /// Track all projects we come across.
-
    All { blocked: HashSet<ProjId> },
+
    All { blocked: HashSet<Id> },
    /// Track a static list of projects.
-
    Allowed(HashSet<ProjId>),
+
    Allowed(HashSet<Id>),
}

impl Default for ProjectTracking {
@@ -54,9 +54,9 @@ pub enum RemoteTracking {
    #[default]
    DelegatesOnly,
    /// Track all remotes.
-
    All { blocked: HashSet<UserId> },
+
    All { blocked: HashSet<PublicKey> },
    /// Track a specific list of users as well as the project delegates.
-
    Allowed(HashSet<UserId>),
+
    Allowed(HashSet<PublicKey>),
}

/// Protocol configuration.
@@ -98,26 +98,26 @@ impl Config {
        self.connect.contains(addr)
    }

-
    pub fn is_tracking(&self, proj: &ProjId) -> bool {
+
    pub fn is_tracking(&self, id: &Id) -> bool {
        match &self.project_tracking {
-
            ProjectTracking::All { blocked } => !blocked.contains(proj),
-
            ProjectTracking::Allowed(projs) => projs.contains(proj),
+
            ProjectTracking::All { blocked } => !blocked.contains(id),
+
            ProjectTracking::Allowed(ids) => ids.contains(id),
        }
    }

    /// Track a project. Returns whether the policy was updated.
-
    pub fn track(&mut self, proj: ProjId) -> bool {
+
    pub fn track(&mut self, id: Id) -> bool {
        match &mut self.project_tracking {
            ProjectTracking::All { .. } => false,
-
            ProjectTracking::Allowed(projs) => projs.insert(proj),
+
            ProjectTracking::Allowed(ids) => ids.insert(id),
        }
    }

    /// Untrack a project. Returns whether the policy was updated.
-
    pub fn untrack(&mut self, proj: ProjId) -> bool {
+
    pub fn untrack(&mut self, id: Id) -> bool {
        match &mut self.project_tracking {
-
            ProjectTracking::All { blocked } => blocked.insert(proj),
-
            ProjectTracking::Allowed(projs) => projs.remove(&proj),
+
            ProjectTracking::All { blocked } => blocked.insert(id),
+
            ProjectTracking::Allowed(ids) => ids.remove(&id),
        }
    }
}
modified node/src/protocol/message.rs
@@ -4,7 +4,7 @@ use git_url::Url;
use serde::{Deserialize, Serialize};

use crate::crypto;
-
use crate::identity::{ProjId, UserId};
+
use crate::identity::Id;
use crate::protocol::{Context, NodeId, Timestamp, PROTOCOL_VERSION};
use crate::storage;
use crate::storage::refs::SignedRefs;
@@ -87,11 +87,11 @@ pub enum Message {
        announcement: NodeAnnouncement,
    },
    /// Get a peer's inventory.
-
    GetInventory { ids: Vec<ProjId> },
+
    GetInventory { ids: Vec<Id> },
    /// Send our inventory to a peer. Sent in response to [`Message::GetInventory`].
    /// Nb. This should be the whole inventory, not a partial update.
    Inventory {
-
        inv: Vec<ProjId>,
+
        inv: Vec<Id>,
        timestamp: Timestamp,
        /// Original peer this inventory came from. We don't set this when we
        /// are the originator, only when relaying.
@@ -101,9 +101,9 @@ pub enum Message {
    /// their view of the project.
    RefsUpdate {
        /// Project under which the refs were updated.
-
        proj: ProjId,
-
        /// User signing.
-
        user: UserId,
+
        id: Id,
+
        /// Signing key.
+
        signer: crypto::PublicKey,
        /// Updated refs.
        refs: SignedRefs<crypto::Unverified>,
    },
@@ -144,7 +144,7 @@ impl Message {
        })
    }

-
    pub fn get_inventory(ids: impl Into<Vec<ProjId>>) -> Self {
+
    pub fn get_inventory(ids: impl Into<Vec<Id>>) -> Self {
        Self::GetInventory { ids: ids.into() }
    }
}
modified node/src/protocol/peer.rs
@@ -194,13 +194,20 @@ impl Peer {
                    }));
                }
            }
-
            (PeerState::Negotiated { git, .. }, Message::RefsUpdate { proj, user, refs }) => {
-
                if refs.verified(&user).is_ok() {
+
            (
+
                PeerState::Negotiated { git, .. },
+
                Message::RefsUpdate {
+
                    id: proj,
+
                    signer,
+
                    refs,
+
                },
+
            ) => {
+
                if refs.verified(&signer).is_ok() {
                    // TODO: Buffer/throttle fetches.
                    // TODO: Also pass the updated refs so that we can check whether
                    // we need to fetch or not.
                    // TODO: Check that the refs are valid.
-
                    if ctx.process_refs_update(&proj, &user, git) {
+
                    if ctx.process_refs_update(&proj, &signer, git) {
                        // TODO: If refs were updated, propagate message to peers.
                    }
                } else {
modified node/src/rad.rs
@@ -6,7 +6,7 @@ use thiserror::Error;

use crate::crypto::Verified;
use crate::git;
-
use crate::identity::ProjId;
+
use crate::identity::Id;
use crate::storage::git::RADICLE_ID_REF;
use crate::storage::refs::SignedRefs;
use crate::storage::{BranchName, ReadRepository as _, WriteRepository as _};
@@ -39,7 +39,7 @@ pub fn init<'r, S: storage::WriteStorage<'r>>(
    description: &str,
    default_branch: BranchName,
    storage: S,
-
) -> Result<(ProjId, SignedRefs<Verified>), InitError> {
+
) -> Result<(Id, SignedRefs<Verified>), InitError> {
    let pk = storage.public_key();
    let delegate = identity::Delegate {
        // TODO: Use actual user name.
@@ -114,11 +114,11 @@ pub enum CheckoutError {
    #[error("storage: {0}")]
    Storage(#[from] storage::Error),
    #[error("project `{0}` was not found in storage")]
-
    NotFound(ProjId),
+
    NotFound(Id),
}

pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
-
    proj: &ProjId,
+
    proj: &Id,
    path: P,
    storage: S,
) -> Result<git2::Repository, CheckoutError> {
modified node/src/storage.rs
@@ -18,13 +18,13 @@ use crate::crypto::{self, PublicKey, Unverified, Verified};
use crate::git::Url;
use crate::git::{RefError, RefStr};
use crate::identity;
-
use crate::identity::{ProjId, ProjIdError, Project, UserId};
+
use crate::identity::{Id, IdError, Project};
use crate::storage::refs::Refs;

use self::refs::SignedRefs;

pub type BranchName = String;
-
pub type Inventory = Vec<ProjId>;
+
pub type Inventory = Vec<Id>;

/// Storage error.
#[derive(Error, Debug)]
@@ -38,7 +38,7 @@ pub enum Error {
    #[error("git: {0}")]
    Git(#[from] git2::Error),
    #[error("id: {0}")]
-
    ProjId(#[from] ProjIdError),
+
    Id(#[from] IdError),
    #[error("i/o: {0}")]
    Io(#[from] io::Error),
    #[error("doc: {0}")]
@@ -47,7 +47,7 @@ pub enum Error {
    InvalidHead,
}

-
pub type RemoteId = UserId;
+
pub type RemoteId = PublicKey;

/// Project remotes. Tracks the git state of a project.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -113,7 +113,7 @@ impl<V> From<Remotes<V>> for HashMap<RemoteId, Refs> {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Remote<V> {
    /// ID of remote.
-
    pub id: UserId,
+
    pub id: PublicKey,
    /// Git references published under this remote, and their hashes.
    pub refs: SignedRefs<V>,
    /// Whether this remote is of a project delegate.
@@ -124,7 +124,7 @@ pub struct Remote<V> {
}

impl<V> Remote<V> {
-
    pub fn new(id: UserId, refs: impl Into<SignedRefs<V>>) -> Self {
+
    pub fn new(id: PublicKey, refs: impl Into<SignedRefs<V>>) -> Self {
        Self {
            id,
            refs: refs.into(),
@@ -161,14 +161,14 @@ impl Remote<Verified> {
pub trait ReadStorage {
    fn public_key(&self) -> &PublicKey;
    fn url(&self) -> Url;
-
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error>;
+
    fn get(&self, proj: &Id) -> Result<Option<Project>, Error>;
    fn inventory(&self) -> Result<Inventory, Error>;
}

pub trait WriteStorage<'r>: ReadStorage {
    type Repository: WriteRepository<'r>;

-
    fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error>;
+
    fn repository(&self, proj: &Id) -> Result<Self::Repository, Error>;
    fn sign_refs(&self, repository: &Self::Repository) -> Result<SignedRefs<Verified>, Error>;
}

@@ -203,7 +203,7 @@ where
    T: Deref<Target = S>,
    S: ReadStorage + 'static,
{
-
    fn public_key(&self) -> &UserId {
+
    fn public_key(&self) -> &PublicKey {
        self.deref().public_key()
    }

@@ -215,7 +215,7 @@ where
        self.deref().inventory()
    }

-
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error> {
+
    fn get(&self, proj: &Id) -> Result<Option<Project>, Error> {
        self.deref().get(proj)
    }
}
@@ -227,7 +227,7 @@ where
{
    type Repository = S::Repository;

-
    fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error> {
+
    fn repository(&self, proj: &Id) -> Result<Self::Repository, Error> {
        self.deref().repository(proj)
    }

modified node/src/storage/git.rs
@@ -11,7 +11,7 @@ pub use radicle_git_ext::Oid;
use crate::crypto::{Signer, Verified};
use crate::git;
use crate::identity::{self, IDENTITY_PATH};
-
use crate::identity::{ProjId, Project, UserId};
+
use crate::identity::{Id, Project, PublicKey};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
use crate::storage::{
@@ -38,7 +38,7 @@ impl fmt::Debug for Storage {
}

impl ReadStorage for Storage {
-
    fn public_key(&self) -> &UserId {
+
    fn public_key(&self) -> &PublicKey {
        self.signer.public_key()
    }

@@ -50,7 +50,7 @@ impl ReadStorage for Storage {
        }
    }

-
    fn get(&self, id: &ProjId) -> Result<Option<Project>, Error> {
+
    fn get(&self, id: &Id) -> Result<Option<Project>, Error> {
        // TODO: Don't create a repo here if it doesn't exist?
        // Perhaps for checking we could have a `contains` method?
        let local = self.public_key();
@@ -83,7 +83,7 @@ impl ReadStorage for Storage {
impl<'r> WriteStorage<'r> for Storage {
    type Repository = Repository;

-
    fn repository(&self, proj: &ProjId) -> Result<Self::Repository, Error> {
+
    fn repository(&self, proj: &Id) -> Result<Self::Repository, Error> {
        Repository::open(self.path.join(proj.to_string()))
    }

@@ -132,12 +132,12 @@ impl Storage {
        }
    }

-
    pub fn projects(&self) -> Result<Vec<ProjId>, Error> {
+
    pub fn projects(&self) -> Result<Vec<Id>, Error> {
        let mut projects = Vec::new();

        for result in fs::read_dir(&self.path)? {
            let path = result?;
-
            let id = ProjId::try_from(path.file_name())?;
+
            let id = Id::try_from(path.file_name())?;

            projects.push(id);
        }
@@ -423,7 +423,7 @@ mod tests {
        let mut rng = fastrand::Rng::new();
        let signer = MockSigner::new(&mut rng);
        let storage = Storage::open(tmp.path(), signer).unwrap();
-
        let proj_id = arbitrary::gen::<ProjId>(1);
+
        let proj_id = arbitrary::gen::<Id>(1);
        let alice = *storage.public_key();
        let project = storage.repository(&proj_id).unwrap();
        let backend = &project.backend;
modified node/src/test/arbitrary.rs
@@ -11,7 +11,7 @@ use crate::crypto::{self, Signer};
use crate::crypto::{PublicKey, SecretKey};
use crate::git;
use crate::hash;
-
use crate::identity::{Delegate, Did, Doc, ProjId, Project};
+
use crate::identity::{Delegate, Did, Doc, Id, Project};
use crate::storage;
use crate::storage::refs::Refs;
use crate::test::storage::MockStorage;
@@ -155,10 +155,10 @@ impl Arbitrary for MockSigner {
    }
}

-
impl Arbitrary for ProjId {
+
impl Arbitrary for Id {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let digest = hash::Digest::arbitrary(g);
-
        ProjId::from(digest)
+
        Id::from(digest)
    }
}

modified node/src/test/fixtures.rs
@@ -1,7 +1,7 @@
use std::path::Path;

use crate::git;
-
use crate::identity::ProjId;
+
use crate::identity::Id;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, WriteStorage};
use crate::test::arbitrary;
@@ -9,7 +9,7 @@ use crate::test::crypto::MockSigner;

pub fn storage<P: AsRef<Path>>(path: P) -> Storage {
    let path = path.as_ref();
-
    let proj_ids = arbitrary::set::<ProjId>(3..5);
+
    let proj_ids = arbitrary::set::<Id>(3..5);
    let signers = arbitrary::set::<MockSigner>(1..3);
    let mut storages = signers
        .into_iter()
modified node/src/test/handle.rs
@@ -2,29 +2,29 @@ use std::sync::{Arc, Mutex};

use crate::client::handle::traits;
use crate::client::handle::Error;
-
use crate::identity::ProjId;
+
use crate::identity::Id;
use crate::protocol;
use crate::protocol::FetchLookup;

#[derive(Default, Clone)]
pub struct Handle {
-
    pub updates: Arc<Mutex<Vec<ProjId>>>,
+
    pub updates: Arc<Mutex<Vec<Id>>>,
}

impl traits::Handle for Handle {
-
    fn fetch(&self, _id: ProjId) -> Result<FetchLookup, Error> {
+
    fn fetch(&self, _id: Id) -> Result<FetchLookup, Error> {
        Ok(FetchLookup::NotFound)
    }

-
    fn track(&self, _id: ProjId) -> Result<bool, Error> {
+
    fn track(&self, _id: Id) -> Result<bool, Error> {
        Ok(true)
    }

-
    fn untrack(&self, _id: ProjId) -> Result<bool, Error> {
+
    fn untrack(&self, _id: Id) -> Result<bool, Error> {
        Ok(true)
    }

-
    fn updated(&self, id: ProjId) -> Result<(), Error> {
+
    fn updated(&self, id: Id) -> Result<(), Error> {
        self.updates.lock().unwrap().push(id);

        Ok(())
modified node/src/test/storage.rs
@@ -2,7 +2,7 @@ use git_url::Url;

use crate::crypto::{PublicKey, Verified};
use crate::git;
-
use crate::identity::{ProjId, Project};
+
use crate::identity::{Id, Project};
use crate::storage::refs;
use crate::storage::{
    Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage,
@@ -38,7 +38,7 @@ impl ReadStorage for MockStorage {
        }
    }

-
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error> {
+
    fn get(&self, proj: &Id) -> Result<Option<Project>, Error> {
        if let Some(proj) = self.inventory.iter().find(|p| p.id == *proj) {
            return Ok(Some(proj.clone()));
        }
@@ -59,7 +59,7 @@ impl ReadStorage for MockStorage {
impl WriteStorage<'_> for MockStorage {
    type Repository = MockRepository;

-
    fn repository(&self, _proj: &ProjId) -> Result<Self::Repository, Error> {
+
    fn repository(&self, _proj: &Id) -> Result<Self::Repository, Error> {
        Ok(MockRepository {})
    }

modified node/src/test/tests.rs
@@ -181,7 +181,7 @@ fn test_tracking() {
        MockStorage::empty(),
        fastrand::Rng::new(),
    );
-
    let proj_id: identity::ProjId = test::arbitrary::gen(1);
+
    let proj_id: identity::Id = test::arbitrary::gen(1);

    let (sender, receiver) = chan::bounded(1);
    alice.command(Command::Track(proj_id.clone(), sender));