@dao-xyz/peerbit
Version:
Distributed p2p database on IPFS
136 lines (120 loc) • 3.38 kB
text/typescript
import {
variant,
option,
field,
vec,
serialize,
fixedArray,
} from "@dao-xyz/borsh";
import { Entry } from "@dao-xyz/peerbit-log";
import { DecryptedThing, Identity } from "@dao-xyz/peerbit-crypto";
import { MaybeSigned } from "@dao-xyz/peerbit-crypto";
import { Log } from "@dao-xyz/peerbit-log";
import { logger as loggerFn } from "@dao-xyz/peerbit-logger";
import { TransportMessage } from "./message.js";
import { v4 as uuid } from "uuid";
const logger = loggerFn({ module: "exchange-heads" });
export class MinReplicas {
get value(): number {
throw new Error("Not implemented");
}
}
(0)
export class AbsolutMinReplicas extends MinReplicas {
_value: number;
constructor(value: number) {
super();
this._value = value;
}
get value() {
return this._value;
}
}
/**
* This thing allows use to faster sync since we can provide
* references that can be read concurrently to
* the entry when doing Log.fromEntry or Log.fromEntryHash
*/
(0)
export class EntryWithRefs<T> {
({ type: Entry })
entry: Entry<T>;
({ type: vec(Entry) })
references: Entry<T>[]; // are some parents to the entry
constructor(properties: { entry: Entry<T>; references: Entry<T>[] }) {
this.entry = properties.entry;
this.references = properties.references;
}
}
([0, 0])
export class ExchangeHeadsMessage<T> extends TransportMessage {
({ type: fixedArray("u8", 32) })
logId: Uint8Array;
({ type: vec(EntryWithRefs) })
heads: EntryWithRefs<T>[];
({ type: option(MinReplicas) })
minReplicas?: MinReplicas;
({ type: fixedArray("u8", 4) })
reserved: Uint8Array = new Uint8Array(4);
constructor(props: {
logId: Uint8Array;
heads: EntryWithRefs<T>[];
minReplicas?: MinReplicas;
}) {
super();
this.id = uuid();
this.logId = props.logId;
this.heads = props.heads;
this.minReplicas = props.minReplicas;
}
}
([0, 1])
export class RequestHeadsMessage extends TransportMessage {
({ type: "string" })
address: string;
constructor(props: { topic: string; address: string }) {
super();
if (props) {
this.address = props.address;
}
}
}
export const createExchangeHeadsMessage = async (
log: Log<any>,
heads: Entry<any>[],
includeReferences: boolean,
identity: Identity | undefined
) => {
const headsSet = new Set(heads);
const headsWithRefs = await Promise.all(
heads.map(async (head) => {
const refs = !includeReferences
? []
: (
await log.getReferenceSamples(head, {
pointerCount: 8,
memoryLimit: 1e6 / heads.length,
})
) // 1mb total limit split on all heads
.filter((r) => !headsSet.has(r)); // pick a proportional amount of refs so we can efficiently load the log. TODO should be equidistant for good performance?
return new EntryWithRefs({
entry: head,
references: refs,
});
})
);
logger.debug(`Send latest heads of '${log.id}'`);
const message = new ExchangeHeadsMessage({
logId: log.id!,
heads: headsWithRefs,
});
const maybeSigned = new MaybeSigned({ data: serialize(message) });
let signedMessage: MaybeSigned<any> = maybeSigned;
if (identity) {
signedMessage = await signedMessage.sign(identity.sign.bind(identity));
}
const decryptedMessage = new DecryptedThing({
data: serialize(signedMessage),
}); // TODO encryption?
return serialize(decryptedMessage);
};