cli: `rad id` allow missing default branch
If a peer had created any references but was also missing the canonical branch, the `rad id update` command will fail when adding that peer as a delegate. Instead, missing delegates and missing default branches are tracked during verification. If the number of missing delegates means the `threshold` cannot be met, then the errors are returned. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
dc34eafdf4
commit
81a3bc3d1b
|
|
@ -0,0 +1,48 @@
|
|||
In some cases, a peer can create references, which includes `rad/sigrefs`,
|
||||
without having pushed the canonical default branch. For example, Bob can create
|
||||
an issue in the repository:
|
||||
|
||||
``` ~bob
|
||||
$ rad issue open --title "Add Bob as a delegate" --description "We agreed to add me as a delegate, so I am creating an issue to track that work" --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
╭──────────────────────────────────────────────────────────────╮
|
||||
│ Title Add Bob as a delegate │
|
||||
│ Issue f12d512c51d30429f7916db038ae0360e2e938c2 │
|
||||
│ Author bob (you) │
|
||||
│ Status open │
|
||||
│ │
|
||||
│ We agreed to add me as a delegate, so I am creating an issue │
|
||||
│ to track that work │
|
||||
╰──────────────────────────────────────────────────────────────╯
|
||||
✓ Synced with 1 node(s)
|
||||
```
|
||||
|
||||
and if we inspect Alice's references, then we will see the following:
|
||||
|
||||
``` ~alice
|
||||
$ rad inspect --refs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
└── refs
|
||||
├── cobs
|
||||
│ └── xyz.radicle.id
|
||||
│ └── 0656c217f917c3e06234771e9ecae53aba5e173e
|
||||
├── heads
|
||||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
└── sigrefs
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
└── refs
|
||||
├── cobs
|
||||
│ └── xyz.radicle.issue
|
||||
│ └── f12d512c51d30429f7916db038ae0360e2e938c2
|
||||
└── rad
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
Despite not having the canonical branch, Alice should still be able to add Bob
|
||||
as a delegate, since a threshold of 1 can still be reached:
|
||||
|
||||
``` ~alice
|
||||
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
|
||||
7be665f9fccba97abb21b2fa85a6fd3181c72858
|
||||
```
|
||||
|
|
@ -8,9 +8,9 @@ can be updated.
|
|||
``` ~alice (fail)
|
||||
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
|
||||
✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
✗ Error: a threshold of 2 delegates cannot be met, found 1 delegate(s) and the following delegates are missing [did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk]
|
||||
✗ Error: the threshold of 2 delegates cannot be met..
|
||||
✗ Error: the delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk is missing
|
||||
✗ Hint: run `rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to follow this missing peer
|
||||
✗ Hint: run `rad sync -f` to attempt to fetch the newly followed peers
|
||||
✗ Error: fatal: refusing to update identity document
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -441,6 +441,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
|
||||
if let Some(errs) = verify_delegates(&proposal, &repo)? {
|
||||
term::error(format!("failed to verify delegates for {rid}"));
|
||||
term::error(format!(
|
||||
"the threshold of {} delegates cannot be met..",
|
||||
proposal.threshold
|
||||
));
|
||||
for e in errs {
|
||||
e.print();
|
||||
}
|
||||
|
|
@ -761,15 +765,14 @@ fn print_diff(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum VerificationError {
|
||||
MissingDefaultBranch {
|
||||
branch: radicle::git::RefString,
|
||||
did: Did,
|
||||
},
|
||||
InsufficientDelegates {
|
||||
threshold: usize,
|
||||
met: usize,
|
||||
missing: Vec<Did>,
|
||||
MissingDelegate {
|
||||
did: Did,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -781,21 +784,11 @@ impl VerificationError {
|
|||
term::format::secondary(branch),
|
||||
term::format::did(did)
|
||||
)),
|
||||
VerificationError::InsufficientDelegates {
|
||||
threshold,
|
||||
met,
|
||||
missing,
|
||||
} => {
|
||||
term::error(format!(
|
||||
"a threshold of {threshold} delegates cannot be met, found {met} delegate(s) and the following delegates are missing [{}]",
|
||||
missing.iter().map(|did| did.to_string()).collect::<Vec<_>>().join(","),
|
||||
VerificationError::MissingDelegate { did } => {
|
||||
term::error(format!("the delegate {did} is missing"));
|
||||
term::hint(format!(
|
||||
"run `rad follow {did}` to follow this missing peer"
|
||||
));
|
||||
for did in missing {
|
||||
term::hint(format!(
|
||||
"run `rad follow {did}` to follow this missing peer"
|
||||
));
|
||||
}
|
||||
term::hint("run `rad sync -f` to attempt to fetch the newly followed peers")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -811,33 +804,23 @@ where
|
|||
let dids = &proposal.delegates;
|
||||
let threshold = proposal.threshold;
|
||||
let (canonical, _) = repo.canonical_head()?;
|
||||
let mut errors = Vec::with_capacity(dids.len());
|
||||
let mut local_delegates = 0;
|
||||
let mut missing = Vec::with_capacity(dids.len());
|
||||
|
||||
for did in dids {
|
||||
match refs::SignedRefsAt::load((*did).into(), repo)? {
|
||||
None => {
|
||||
missing.push(*did);
|
||||
missing.push(VerificationError::MissingDelegate { did: *did });
|
||||
}
|
||||
Some(refs::SignedRefsAt { sigrefs, .. }) => {
|
||||
if sigrefs.get(&canonical).is_none() {
|
||||
errors.push(VerificationError::MissingDefaultBranch {
|
||||
missing.push(VerificationError::MissingDefaultBranch {
|
||||
branch: canonical.to_ref_string(),
|
||||
did: *did,
|
||||
})
|
||||
} else {
|
||||
local_delegates += 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if local_delegates < threshold {
|
||||
errors.push(VerificationError::InsufficientDelegates {
|
||||
threshold,
|
||||
met: local_delegates,
|
||||
missing,
|
||||
});
|
||||
}
|
||||
Ok((!errors.is_empty()).then_some(errors))
|
||||
Ok((dids.len() - missing.len() < threshold).then_some(missing))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,56 @@ fn rad_id_threshold() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_id_threshold_soft_fork() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node(config::node("alice"));
|
||||
let bob = environment.node(config::node("bob"));
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let working = working.path();
|
||||
let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
||||
// Setup a test repository.
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
||||
test(
|
||||
"examples/rad-init.md",
|
||||
working.join("alice"),
|
||||
Some(&alice.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
let events = bob.handle.events();
|
||||
bob.handle.seed(acme, Scope::All).unwrap();
|
||||
alice.connect(&bob).converge([&bob]);
|
||||
|
||||
events
|
||||
.wait(
|
||||
|e| matches!(e, Event::RefsFetched { .. }).then_some(()),
|
||||
time::Duration::from_secs(6),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
formula(&environment.tmp(), "examples/rad-id-threshold-soft-fork.md")
|
||||
.unwrap()
|
||||
.home(
|
||||
"alice",
|
||||
working.join("alice"),
|
||||
[("RAD_HOME", alice.home.path().display())],
|
||||
)
|
||||
.home(
|
||||
"bob",
|
||||
working.join("bob"),
|
||||
[("RAD_HOME", bob.home.path().display())],
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_id_update_delete_field() {
|
||||
let mut environment = Environment::new();
|
||||
|
|
|
|||
Loading…
Reference in New Issue