in src/governance/ccf-app/js/src/endpoints/documents.ts [144:265]
export function getDocument(
request: ccfapp.Request
): ccfapp.Response<GetDocumentResponse> | ccfapp.Response<ErrorResponse> {
const id = request.params.documentId;
// Check if the document is already accepted.
if (acceptedDocumentsStore.has(id)) {
const documentItem = acceptedDocumentsStore.get(id);
const seqno = acceptedDocumentsStore.getVersionOfPreviousWrite(id);
const view = ccf.consensus.getViewForSeqno(seqno);
if (view == null) {
return {
statusCode: 503,
body: new ErrorResponse(
"ViewNotKnown",
"View for given sequence number not known to the node at this time."
)
};
}
const version = view + "." + seqno;
const finalVotes: { memberId: string; vote: boolean }[] = [];
if (documentItem.finalVotes !== undefined) {
for (const [memberId, vote] of Object.entries(documentItem.finalVotes)) {
finalVotes.push({
memberId: memberId,
vote: vote === true ? true : false
});
}
}
const body: GetDocumentResponse = {
id: id,
version: version,
state: "Accepted",
contractId: documentItem.contractId,
data: documentItem.data,
proposalId: documentItem.proposalId,
finalVotes: finalVotes
};
return { body };
}
// Check if the document is currently associated with an open proposal.
let proposedDocument;
proposalsStore.forEach((v, k) => {
const proposal = ccf.bufToJsonCompatible(v) as ProposalStoreItem;
proposal.actions.forEach((value) => {
if (value.name === "set_document") {
const args = value.args as SetDocumentArgs;
if (args.documentId === id) {
const proposalInfo = ccf.bufToJsonCompatible(
proposalsInfoStore.get(k)
) as ProposalInfoItem;
if (proposalInfo.state == "Open") {
const body: GetDocumentResponse = {
id: id,
state: "Proposed",
contractId: args.document.contractId,
data: args.document.data,
version: args.document.version,
proposalId: k
};
proposedDocument = { body };
return false; // break out of the loop.
}
}
}
});
});
if (proposedDocument != null) {
return proposedDocument;
}
if (documentsStore.has(id)) {
const documentItem = documentsStore.get(id);
// Capture both seqno (version) and view to create version semantics.
// Apart from getVersionOfPreviousWrite(key) we also want to call getViewForSeqno(seqno) to
// get and incorporate the view into the version because the following situation could take place:
// getVersionOfPreviousWrite(k) -> 5
// Client goes to prepare write conditional on version being 5
// Network rolls back to 3 after primary crashes, elects new leader
// 4 and 5 happen, 5 unluckily writes to k also
// Client request arrives, expects last write to be at 5, proceeds - but the value is now different
// If you capture:
// getVersionOfPreviousWrite(k) -> 5
// getViewForSeqno(5) -> 2
// And place (and check) the expectation that the last write for the key must be at 5 in view 2,
// then this cannot happen.
const seqno = documentsStore.getVersionOfPreviousWrite(id);
const view = ccf.consensus.getViewForSeqno(seqno);
if (view == null) {
return {
statusCode: 503,
body: new ErrorResponse(
"ViewNotKnown",
"View for given sequence number not known to the node at this time."
)
};
}
const version = view + "." + seqno;
const body: GetDocumentResponse = {
id: id,
state: "Draft",
version: version,
contractId: documentItem.contractId,
data: documentItem.data,
proposalId: ""
};
return { body };
}
return {
statusCode: 404,
body: new ErrorResponse(
"DocumentNotFound",
"A document with the specified id was not found."
)
};
}