Skip to content

Initial implementation and tests #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 79 commits into from
Feb 9, 2021
Merged

Initial implementation and tests #1

merged 79 commits into from
Feb 9, 2021

Conversation

RangerMauve
Copy link
Collaborator

Fixes # .

Changes proposed in this pull request:

@consento-org/maintainer

src/member.ts Outdated
const req = this.getRequest(item.id);
req.addResponse(id, item);
let neededSignatures = this.knownMembers.length;
if (req.operation === "remove") neededSignatures--;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be always n-1?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do n-1 can we avoid ambiguity? Like, if we have A,B,C,D,E, if A,B,C have signed, and D/E aren't connected to each other / accept, they'll both think they're the final needed signature and cause a fork.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If n-1 doesn't work in that case it doesn't work in the other case, as two devices can mutually remove each other, it would also create that ambiguity.

Copy link
Member

@martinheidegger martinheidegger Dec 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a conversation about allowing forks but maybe resolving equal forks?! In example: D & E approve R1 creating Forks R1D and R1E and if they sync and notice equality they decide for the alphabetically lower one (R1D).

src/member.ts Outdated

if (hasProcessed) {
const otherRun = this.processFeeds()
return hasProcessed || otherRun;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, endless recursion possible, right? 😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, what's the condition that causes it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing at the moment, but my eyes look with stress 💦 on the code and I would be constantly worried if it introduces endless recursions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a different approach I should take here? The current structure recurses until all the feeds have been processed (more specifically, until no items are processable).

src/member.ts Outdated
// Get new data from members to knownFeeds
for (const id of Object.keys(member.knownFeeds)) {
const feed = member.knownFeeds[id];
const ownCopy = this.getFeedFor(id);
Copy link
Member

@martinheidegger martinheidegger Dec 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a validate template here to validate if the feed data that is added keeps being added. I think this is important to figure out even in an early stage because it allows us to figure out if we can identify a valid from a corrupt feed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this require the signvector stuff?

Copy link
Member

@martinheidegger martinheidegger Dec 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sign-vectors are part of this too. Yes, but there is a much simpler case: if there is a feed of a member that has been removed or was never added. Also, there is a thin possibility that the sign-vectors fail (and a fork is created). i.e. malfunctioning of a client: Fork detection should also be part of it.

Note: The central log may be a tool to make it easier/possible to detect member-feed forks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, mind pseudocodeing how the verification would look and what the test for forking would look like?

Copy link
Member

@martinheidegger martinheidegger Dec 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function isIncomingFeedForked (localFeed, feed) {
  // Look at the merkle tree and see if the feed was forked at a previous step
}

function isIncomingFeedSignVectorCorrect (feed, startIndex) {
  let vector = createSignVectorFor(feed.get(startIndex))
  for (let i = startIndex+1; i<feed.length; i++) {
    if (!signVector.validate(feed.get(i))) return false
  }
  return true
}

function isIncomingFeedCorrect (feed) {
  // testing "correct-ness" of requests, i.e. proper msgpack/protobuf object; size limit not exceeded; removes feed that actually existed. Does the signature exist for the request? etc.
}

function verifyFeed (localFeed, feed, startIndex, peer) {
  if (isIncomingFeedForked(localFeed, feed)) {
    // Red alert
    requestRemoveMember({ cause: 'fork-detected' })
    return false
  }
  if (!isIncomingFeedSignVectorCorrect(feed, startIndex)) {
    addToBlackList(peer)
    return false
  }
  if(!isIncomingFeedCorrect(feed)) {
    // orange alert
    requestRemoveMember({ cause: 'incorrect-client' })
    return false
  }
  return true
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet, thank you!

test/index.ts Outdated
tape("Able to initialize a member", (t) => {
const member = new Member();

member.processFeeds();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't processFeeds() be part of the syncing process? What advantage do we get of separating those?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main thing here was the auto-accept function meant that processing feeds would trigger member adds, and I was hoping to have more control of when that happened. I can refactor to work around that so that control of accepting could be outside of syncing feeds.

Other consideration was that I wanted a place for members to process requests and responses that they generated themselves 1. for the initial member case and 2. to remove the need for extra logic for that bit. Down to add proccesFeeds at the end of sync, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can refactor to work around that so that control of accepting could be outside of syncing feeds.

Structurally it is probably for the best, though I don't think we need to use manual confirmation in our first real scenario. The reason why I brought this up is because in the sync process it may be possible to avoid some chance of forks by having one member sync first and process the entries before syncing everything - including the need entries - with the other peer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm thinking that I'll have a getPendingRequests() method and a acceptPendingRequests()

src/member.ts Outdated
}

finish() {
this.finished = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memo: This needs to be established in the final solution using a log statement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on this point?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a finished = true statement works for this simulation but in an actual case we should probably have a log statement that looks like { request: 'abcd', finished: true } in order to recover the state from the log itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not 100% sure what you mean regarding recovering state from the log. If we don't persist request state anywhere we'd need to traverse the entire log again and that means that finished would be derived from there again.

@RangerMauve
Copy link
Collaborator Author

Working on refactoring to change how processFeeds works, and then I'll add more tests

@martinheidegger
Copy link
Member

Without having a central feed its quite challenging for me to figure out if a fork happened. Is there a good way to verify that without a central log?

Another thing: When adding feeds, the information should not just contain their ID's but also the verification vector for the first block

@RangerMauve
Copy link
Collaborator Author

Without a central feed there is no forking.

Since everyone needs to agree on a history and you derive members by traversing each others feeds and verifying that signatures happened. If someone tires to fork their own member feed (assuming they bypass the signvector layer of security) then at most they can prevent new peers from getting a valid history (until the fork is detected and the invalid feed is purged).

Agreed on the verification vector stuff. I'll look at incorporating sign vectors after this initial refactor.

@RangerMauve
Copy link
Collaborator Author

TODO:

  • Add sign vectors
  • Verify sign vectors in sync
  • When removing specify index to remove at so that they won't be processed after
  • Add tests for forks
  • Add tests fo rconflicting requests
  • Add tests so that external peers always process feeds in the same way

@RangerMauve
Copy link
Collaborator Author

Added some sims, so far so good.

Talking with Martin last week, we determined we could be running into issues if we allow members to have multiple in-flight requests.

Gonna write up some thoughts on how that works and make some diagrams of what it would look like.

@martinheidegger martinheidegger marked this pull request as ready for review February 9, 2021 01:21
Copy link
Member

@martinheidegger martinheidegger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can merge it for now. more progress would be good to be made in issues.

@RangerMauve RangerMauve merged commit 619bbcd into main Feb 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants