Skip to content
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

Hypercore v1 #5

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"coverage": "c8 --reporter=html --reporter=lcov npm run test",
"build": "tsc",
"protobuf": "protocol-buffers feeditem.proto -o src/feeditem.js",
"postinstall": "husky install"
"postinstall": "husky-run install"
martinheidegger marked this conversation as resolved.
Show resolved Hide resolved
},
"eslintConfig": {
"extends": "standard-with-typescript",
Expand All @@ -33,11 +33,13 @@
"homepage": "https://github.com/consento-org/permissions-system-sim#readme",
"dependencies": {
"@consento/hlc": "^2.1.0",
"@types/node": "^14.14.25"
"@types/node": "^14.14.25",
"hyper-sdk": "^3.0.7"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.14.2",
"c8": "^7.5.0",
"delay": "^5.0.0",
"eslint": "^7.19.0",
"eslint-config-standard-with-typescript": "^20.0.0",
"eslint-plugin-import": "^2.22.1",
Expand Down
45 changes: 33 additions & 12 deletions src/Feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import {
} from './FeedItem'
import { randomBytes } from 'crypto'
import { Timestamp } from '@consento/hlc'
import { Sync } from './Sync'

export type FeedLoader = (id: ID) => Promise<Feed>

export async function defaultFeedLoader (id: ID): Promise<Feed> {
return new Feed(id)
}

export class Feed {
readonly items = new Array<FeedItem>()
Expand All @@ -20,27 +27,37 @@ export class Feed {
}

// Return value of `true` means stuff got synced
sync (other: Feed): boolean {
async sync (other: Sync): Promise<boolean> {
// TODO: detect potential fork in timestamps
if (other.length > this.length) {
this.items.push(...other.items.slice(this.length))
if (!await other.hasFeed(this.id)) return false

const otherFeed = await other.getFeed(this.id)

if (otherFeed.length > this.length) {
this.items.push(...otherFeed.items.slice(this.length))
return true
}

return false
}

current (): FeedItem {
return this.get(this.index)
async current (): Promise<FeedItem> {
return await this.get(this.index)
}

increment (): void {
this.index++
}

get (index: number): FeedItem {
async get (index: number): Promise<FeedItem> {
return this.items[index]
}

async append (item: FeedItem): Promise<number> {
this.items.push(item)
return this.items.length
}

hasMore (): boolean {
return this.length > 0 && (this.index < this.length)
}
Expand All @@ -49,15 +66,15 @@ export class Feed {
return this.items.length
}

addRequest ({
async addRequest ({
operation,
who,
timestamp
}: {
operation: Operation
who: ID
timestamp: Timestamp
}): Request {
}): Promise<Request> {
const req: Request = {
type: 'request',
// TODO: Use more bytes?
Expand All @@ -67,27 +84,31 @@ export class Feed {
operation,
who
}
this.items.push(req)
await this.append(req)
return req
}

addResponse ({
async addResponse ({
id,
response,
timestamp
}: {
id: ID
response: ResponseType
timestamp: Timestamp
}): Response {
}): Promise<Response> {
const res: Response = {
type: 'response',
id,
from: this.id,
timestamp,
response
}
this.items.push(res)
await this.append(res)
return res
}

async close (): Promise<void> {
// Nothing to do here
}
}
107 changes: 76 additions & 31 deletions src/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,70 @@ import { Permissions } from './Permissions'
import { Sync } from './Sync'
import { randomBytes } from 'crypto'
import { Timestamp } from '@consento/hlc'
import { Feed } from './Feed'
import { Feed, FeedLoader, defaultFeedLoader } from './Feed'

export interface MemberOptions {
id?: ID
initiator?: ID
loadFeed?: FeedLoader
}

export class Member {
readonly feed: Feed
readonly syncState: Sync
readonly id: ID
readonly initiator: ID
_feed?: Feed
_syncState?: Sync
readonly _id: ID
initiator: ID
private readonly loadFeed: FeedLoader

static async create (opts?: MemberOptions): Promise<Member> {
const member = new Member(opts)
await member.init()

return member
}

constructor ({
id = randomBytes(8).toString('hex'),
initiator = id
initiator = id,
loadFeed = defaultFeedLoader
Copy link
Member

Choose a reason for hiding this comment

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

Looking over the code the name loadFeed has been sticking out to me: As a developer using this API: what am I supposed to do? In the current case its like getOrLoadFeed if I understand it correctly but I was wondering if maybe reqestFeed may be appropriate?

}: MemberOptions = {}) {
this.id = id
this._id = id
this.initiator = initiator
this.feed = new Feed(id)
this.loadFeed = loadFeed
}

isInitiator (): boolean {
return this.id === this.initiator
}

async init (): Promise<void> {
this._feed = await this.loadFeed(this._id)

// Workaround for when feeds give you a new ID based on your given ID
// TODO: Clean this up?
if (this.initiator === this._id) this.initiator = this.id

this.syncState = new Sync(initiator)
this._syncState = new Sync(this.initiator, this.loadFeed)

this.syncState.addFeed(this.feed)
await this.syncState.addFeed(this.feed)

if (id === initiator) {
this.requestAdd(this.id)
if (this.isInitiator()) {
await this.requestAdd(this.id)
}
}

get syncState (): Sync {
return this._syncState as Sync
}

get id (): ID {
return this.feed.id
}

get feed (): Feed {
return this._feed as Feed
}

get permissions (): Permissions {
return this.syncState.permissions
}
Expand All @@ -45,52 +79,52 @@ export class Member {
return this.syncState.knownMembers
}

sync (other: Member): void {
this.syncState.sync(other.syncState)
async sync (other: Member): Promise<void> {
await this.syncState.sync(other.syncState)
}

processFeeds (): void {
this.syncState.processFeeds()
async processFeeds (): Promise<void> {
await this.syncState.processFeeds()
}

requestAdd (who: ID): Request {
const req = this.feed.addRequest({
async requestAdd (who: ID): Promise<Request> {
const req = await this.feed.addRequest({
operation: 'add',
who,
timestamp: this.now()
})
this.processFeeds()
await this.processFeeds()
return req
}

requestRemove (who: ID): Request {
const req = this.feed.addRequest({
async requestRemove (who: ID): Promise<Request> {
const req = await this.feed.addRequest({
operation: 'remove',
who,
timestamp: this.now()
})
this.processFeeds()
await this.processFeeds()

return req
}

acceptRequest ({ id }: Request): Response {
async acceptRequest ({ id }: Request): Promise<Response> {
const res = this.feed.addResponse({
id,
response: 'accept',
timestamp: this.now()
})
this.processFeeds()
return res
await this.processFeeds()
return await res
}

denyRequest ({ id }: Request): Response {
const res = this.feed.addResponse({
async denyRequest ({ id }: Request): Promise<Response> {
const res = await this.feed.addResponse({
id,
response: 'deny',
timestamp: this.now()
})
this.processFeeds()
await this.processFeeds()
return res
}

Expand All @@ -107,15 +141,26 @@ export class Member {
})
}

signUnsigned (): Response[] {
async signUnsigned (): Promise<Response[]> {
const toSign = this.getUnsignedRequests()
const responses = []

for (const req of toSign) {
const res = await this.acceptRequest(req)
responses.push(res)
}

const responses = toSign.map((req) => this.acceptRequest(req))
await this.processFeeds()

return responses
}

private now (): Timestamp {
async close (): Promise<void> {
// TODO: Should we clear resources here?
await this.syncState.close()
}

now (): Timestamp {
return this.syncState.permissions.clock.now()
}
}
2 changes: 0 additions & 2 deletions src/MemberList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ export class MemberList {

add (member: ID, timestamp: Timestamp): void {
this._state.set(timestamp, member, 'added')
this.recalculate()
}

remove (member: ID, timestamp: Timestamp): void {
this._state.set(timestamp, member, 'removed')
this.recalculate()
}

added (at? : Timestamp): ReadonlySet<ID> {
Expand Down
5 changes: 3 additions & 2 deletions src/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ export class Permissions {
throw new Error('Cant accept own request.')
}
const signatures = this.addSignature(response)
if (signatures >= this.getRequiredSignatures(openRequest)) {
const required = this.getRequiredSignatures(openRequest)
if (signatures >= required) {
this.finishRequest(openRequest)
}
}
Expand Down Expand Up @@ -145,7 +146,7 @@ export class Permissions {
private addSignature (response: Response): number {
const signatures = this.signatures.get(response.id)
if (signatures === undefined) {
this.signatures.set(response.id, new Set(response.from))
this.signatures.set(response.id, new Set([response.from]))
return 1
}
if (signatures.has(response.from)) {
Expand Down
Loading