Add nodes and edges to CredView (#1839)

This adds support for retrieving cred-augmented nodes and edges to the
CredView class. These methods wrap the underlying Graph methods, and
return nodes and edges that also include relevant cred information

Test plan: Unit tests included; yarn test passes.
This commit is contained in:
Dandelion Mané 2020-06-09 16:40:03 -07:00 committed by GitHub
parent 753aec3acf
commit 8436dcee81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 220 additions and 1 deletions

View File

@ -6,6 +6,58 @@ import {type TimelineCredParameters} from "./timeline/params";
import {Graph} from "../core/graph";
import {type PluginDeclarations} from "./pluginDeclaration";
import {get as nullGet} from "../util/null";
import {type EdgeWeight} from "../core/weights";
import type {TimestampMs} from "../util/timestamp";
import {
type NodeWeightEvaluator,
nodeWeightEvaluator,
type EdgeWeightEvaluator,
edgeWeightEvaluator,
} from "../core/algorithm/weightEvaluator";
import {
type NodeAddressT,
type EdgeAddressT,
type Node as GraphNode,
type Edge as GraphEdge,
} from "../core/graph";
import type {
NodeCredSummary,
NodeCredOverTime,
EdgeCredSummary,
EdgeCredOverTime,
} from "./credData";
export type CredNode = {|
+address: NodeAddressT,
+description: string,
+minted: number,
+timestamp: TimestampMs | null,
+credSummary: NodeCredSummary,
+credOverTime: NodeCredOverTime | null,
|};
export type CredEdge = {|
+address: EdgeAddressT,
+src: CredNode,
+dst: CredNode,
+rawWeight: EdgeWeight,
+credSummary: EdgeCredSummary,
+credOverTime: EdgeCredOverTime | null,
+timestamp: TimestampMs,
|};
export type EdgesOptions = {|
// An edge address prefix. Only show edges whose addresses match this prefix.
+addressPrefix?: EdgeAddressT,
// A node address prefix. Only show edges whose src matches
// this prefix.
+srcPrefix?: NodeAddressT,
// A node address prefix. Only show edges whose dst matches
// this prefix.
+dstPrefix?: NodeAddressT,
|};
/**
* The CredView is an interface for Graph-aware queries over a CredResult.
*
@ -14,10 +66,21 @@ import {type PluginDeclarations} from "./pluginDeclaration";
* such queries convenient.
*/
export class CredView {
_credResult: CredResult;
+_credResult: CredResult;
+_nodeAddressToIndex: Map<NodeAddressT, number>;
+_edgeAddressToIndex: Map<EdgeAddressT, number>;
+_nodeEvaluator: NodeWeightEvaluator;
+_edgeEvaluator: EdgeWeightEvaluator;
constructor(result: CredResult) {
this._credResult = result;
const {weights, graph} = result.weightedGraph;
const nodes = Array.from(graph.nodes());
const edges = Array.from(graph.edges({showDangling: false}));
this._nodeAddressToIndex = new Map(nodes.map((n, i) => [n.address, i]));
this._edgeAddressToIndex = new Map(edges.map((n, i) => [n.address, i]));
this._nodeEvaluator = nodeWeightEvaluator(weights);
this._edgeEvaluator = edgeWeightEvaluator(weights);
}
graph(): Graph {
@ -39,4 +102,61 @@ export class CredView {
credResult(): CredResult {
return this._credResult;
}
_promoteNode(n: GraphNode): CredNode {
const idx = nullGet(this._nodeAddressToIndex.get(n.address));
const credSummary = this._credResult.credData.nodeSummaries[idx];
const credOverTime = this._credResult.credData.nodeOverTime[idx];
const minted = this._nodeEvaluator(n.address);
return {
timestamp: n.timestampMs,
description: n.description,
address: n.address,
credSummary,
credOverTime,
minted,
};
}
node(a: NodeAddressT): ?CredNode {
const graphNode = this.graph().node(a);
if (graphNode == null) {
return undefined;
}
return this._promoteNode(graphNode);
}
nodes(options?: {|+prefix: NodeAddressT|}): $ReadOnlyArray<CredNode> {
const graphNodes = Array.from(this.graph().nodes(options));
return graphNodes.map((x) => this._promoteNode(x));
}
_promoteEdge(e: GraphEdge): CredEdge {
const idx = nullGet(this._edgeAddressToIndex.get(e.address));
const srcNode = nullGet(this.graph().node(e.src));
const dstNode = nullGet(this.graph().node(e.dst));
const credSummary = this._credResult.credData.edgeSummaries[idx];
const credOverTime = this._credResult.credData.edgeOverTime[idx];
const rawWeight = this._edgeEvaluator(e.address);
return {
timestamp: e.timestampMs,
address: e.address,
src: this._promoteNode(srcNode),
dst: this._promoteNode(dstNode),
credSummary,
credOverTime,
rawWeight,
};
}
edge(a: EdgeAddressT): ?CredEdge {
const graphEdge = this.graph().edge(a);
if (graphEdge == null || this.graph().isDanglingEdge(a)) {
return undefined;
}
return this._promoteEdge(graphEdge);
}
edges(options?: EdgesOptions): $ReadOnlyArray<CredEdge> {
const graphEdges = Array.from(
this.graph().edges({...options, showDangling: false})
);
return graphEdges.map((x) => this._promoteEdge(x));
}
}

View File

@ -1,6 +1,7 @@
// @flow
import {Graph, NodeAddress, EdgeAddress} from "../core/graph";
import {get as nullGet} from "../util/null";
import type {NodeType, EdgeType} from "./types";
import {
type PluginDeclaration,
@ -141,4 +142,102 @@ describe("analysis/credView", () => {
expect(credView.plugins()).toEqual([declaration]);
});
});
describe("nodes", () => {
it("can retrieve a CredNode", async () => {
const {credView, foo1, graph, credResult} = await example();
const {
address,
credOverTime,
credSummary,
description,
minted,
timestamp,
} = nullGet(credView.node(foo1.address));
expect({address, description, timestampMs: timestamp}).toEqual(foo1);
expect(minted).toEqual(2);
const nodeOrder = Array.from(graph.nodes()).map((x) => x.address);
const index = nodeOrder.findIndex((x) => x === foo1.address);
expect(credOverTime).toEqual(credResult.credData.nodeOverTime[index]);
expect(credSummary).toEqual(credResult.credData.nodeSummaries[index]);
});
it("returns undefined for non-existent node", async () => {
const {credView} = await example();
expect(credView.node(NodeAddress.fromParts(["nope"]))).toBe(undefined);
});
it("returns array of all nodes when no arguments provided", async () => {
const {credView, foo1, foo2, user} = await example();
expect(credView.nodes()).toEqual(
[foo1, foo2, user].map((x) => credView.node(x.address))
);
});
it("nodes can filter by prefix", async () => {
const {credView, foo1, foo2, fooType} = await example();
expect(credView.nodes({prefix: fooType.prefix})).toEqual(
[foo1, foo2].map((x) => credView.node(x.address))
);
});
});
describe("edges", () => {
it("can retrieve a CredEdge", async () => {
const {credView, flow1, credResult, graph} = await example();
const {
address,
src,
dst,
credOverTime,
credSummary,
rawWeight,
timestamp,
} = nullGet(credView.edge(flow1.address));
expect({
address,
src: src.address,
dst: dst.address,
timestampMs: timestamp,
}).toEqual(flow1);
const edgeOrder = Array.from(graph.edges({showDangling: false})).map(
(x) => x.address
);
const index = edgeOrder.findIndex((x) => x === flow1.address);
expect(src).toEqual(credView.node(src.address));
expect(dst).toEqual(credView.node(dst.address));
expect(rawWeight).toEqual({forwards: 2, backwards: 3});
expect(credOverTime).toEqual(credResult.credData.edgeOverTime[index]);
expect(credSummary).toEqual(credResult.credData.edgeSummaries[index]);
});
it("returns undefined for non-existent edge", async () => {
const {credView} = await example();
expect(credView.edge(EdgeAddress.fromParts(["nope"]))).toBe(undefined);
});
it("returns undefined for a dangling edge", async () => {
const {credView, dangling} = await example();
expect(credView.edge(dangling.address)).toBe(undefined);
});
it("returns array of all non-dangling edges when no arguments provided", async () => {
const {credView, flow1, flow2, stream1} = await example();
expect(credView.edges()).toEqual(
[flow1, flow2, stream1].map((x) => credView.edge(x.address))
);
});
it("edges can filter by address prefix", async () => {
const {credView, flow1, flow2, flowType} = await example();
expect(credView.edges({addressPrefix: flowType.prefix})).toEqual(
[flow1, flow2].map((x) => credView.edge(x.address))
);
});
it("edges can filter by src prefix", async () => {
const {credView, stream1, userType} = await example();
expect(credView.edges({srcPrefix: userType.prefix})).toEqual([
credView.edge(stream1.address),
]);
});
it("edges can filter by dst prefix", async () => {
const {credView, flow1, flow2, userType} = await example();
expect(credView.edges({dstPrefix: userType.prefix})).toEqual([
credView.edge(flow1.address),
credView.edge(flow2.address),
]);
});
});
});