craterdog.accounting.V1AccountingProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-digital-accounting Show documentation
Show all versions of java-digital-accounting Show documentation
This project defines a set of interfaces and classes that implement digital accounting.
/************************************************************************
* Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. *
************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. *
* *
* This code is free software; you can redistribute it and/or modify it *
* under the terms of The MIT License (MIT), as published by the Open *
* Source Initiative. (See http://opensource.org/licenses/MIT) *
************************************************************************/
package craterdog.accounting;
import craterdog.notary.Notarization;
import craterdog.notary.NotaryCertificate;
import craterdog.notary.NotaryKey;
import craterdog.notary.NotarySeal;
import craterdog.notary.V1NotarizationProvider;
import craterdog.tokens.DigitalToken;
import craterdog.tokens.Tokenization;
import craterdog.tokens.V1TokenizationProvider;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
/**
* This class implements a version 1 provider of the TransactionManagement
interface.
*
* @author Derk Norton
*/
public final class V1AccountingProvider implements Accounting {
static private final XLogger logger = XLoggerFactory.getXLogger(V1AccountingProvider.class);
static private final Notarization notarizationProvider = new V1NotarizationProvider();
static private final Tokenization tokenizationProvider = new V1TokenizationProvider();
@Override
public DigitalTransaction initiateTransaction(URI transactionLocation, URI senderLocation, URI receiverLocation, String transactionType, DigitalToken token, NotaryKey senderKey) {
logger.entry(transactionLocation, senderLocation, receiverLocation, transactionType, token, senderKey);
DigitalTransaction transaction = initiateTransaction(transactionLocation, senderLocation, receiverLocation, null, transactionType, token, null, senderKey);
logger.exit(transaction);
return transaction;
}
@Override
public DigitalTransaction initiateTransaction(URI transactionLocation, URI senderLocation, URI receiverLocation, String transactionType, DigitalToken token, Map additionalAttributes, NotaryKey senderKey) {
logger.entry(transactionLocation, senderLocation, receiverLocation, transactionType, token, additionalAttributes, senderKey);
DigitalTransaction transaction = initiateTransaction(transactionLocation, senderLocation, receiverLocation, null, transactionType, token, additionalAttributes, senderKey);
logger.exit(transaction);
return transaction;
}
@Override
public DigitalTransaction initiateTransaction(URI transactionLocation, URI senderLocation, URI receiverLocation, URI escrowLocation, String transactionType, DigitalToken token, NotaryKey senderKey) {
logger.entry(transactionLocation, senderLocation, receiverLocation, escrowLocation, transactionType, token, senderKey);
DigitalTransaction transaction = initiateTransaction(transactionLocation, senderLocation, receiverLocation, escrowLocation, transactionType, token, null, senderKey);
logger.exit(transaction);
return transaction;
}
@Override
public DigitalTransaction initiateTransaction(URI transactionLocation, URI senderLocation, URI receiverLocation, URI escrowLocation, String transactionType, DigitalToken token, Map additionalAttributes, NotaryKey senderKey) {
logger.entry(transactionLocation, senderLocation, receiverLocation, escrowLocation, transactionType, token, additionalAttributes, senderKey);
logger.debug("Creating the new escrow transaction...");
TransactionAttributes attributes = new TransactionAttributes();
attributes.myLocation = transactionLocation;
attributes.senderLocation = senderLocation;
attributes.receiverLocation = receiverLocation;
attributes.escrowLocation = escrowLocation;
URI documentLocation = token.attributes.myLocation;
String document = token.toString();
attributes.tokenCitation = notarizationProvider.generateDocumentCitation(documentLocation, document);
attributes.transactionType = transactionType;
attributes.watermark = notarizationProvider.generateWatermark(Notarization.VALID_FOR_FOREVER);
if (additionalAttributes != null) {
logger.debug("Adding additional attributes...");
for (Map.Entry attribute : additionalAttributes.entrySet()) {
attributes.put(attribute.getKey(), attribute.getValue());
}
}
logger.debug("Validating the transaction attributes...");
DigitalTransaction transaction = new DigitalTransaction();
transaction.attributes = attributes;
Map errors = new LinkedHashMap<>();
validateTransactionAttributes(transaction, token, errors);
notarizationProvider.throwExceptionOnErrors("invalid.transaction.attributes", errors);
logger.debug("Notarizing the new transaction using the sender signing key...");
String documentType = "Transaction Attributes";
document = attributes.toString();
transaction.senderSeal = notarizationProvider.notarizeDocument(documentType, document, senderKey);
logger.exit(transaction);
return transaction;
}
@Override
public void approveTransaction(DigitalTransaction transaction, DigitalToken token, NotaryCertificate senderCertificate, NotaryKey receiverKey) {
logger.entry(transaction, token, senderCertificate, receiverKey);
logger.debug("Validating the attributes...");
Map errors = new LinkedHashMap<>();
validateTransactionAttributes(transaction, token, errors);
logger.debug("Validating the sender seal using the sender verification key...");
validateSenderSeal(transaction, senderCertificate, errors);
if (transaction.receiverSeal != null) {
logger.error("The transaction is already approved...");
errors.put("transaction.is.already.approved", transaction);
}
notarizationProvider.throwExceptionOnErrors("invalid.transaction", errors);
logger.debug("Notarizing the transaction using the receiver signing key...");
String documentType = "Sender Signature";
String document = transaction.senderSeal.selfSignature;
transaction.receiverSeal = notarizationProvider.notarizeDocument(documentType, document, receiverKey);
logger.exit(transaction);
}
@Override
public void certifyTransaction(DigitalTransaction transaction, DigitalToken token, NotaryCertificate senderCertificate, NotaryCertificate receiverCertificate, NotaryKey escrowKey) {
logger.entry(transaction, token, senderCertificate, receiverCertificate, escrowKey);
logger.debug("Validating the attributes...");
Map errors = new LinkedHashMap<>();
validateTransactionAttributes(transaction, token, errors);
logger.debug("Validating the sender seal using the sender verification key...");
validateSenderSeal(transaction, senderCertificate, errors);
logger.debug("Validating the receiver seal using the receiver verification key...");
validateReceiverSeal(transaction, receiverCertificate, errors);
logger.debug("Checking for an existing escrow seal...");
if (transaction.escrowSeal != null) {
logger.error("The transaction is already certified...");
errors.put("transaction.is.already.certified", transaction);
}
logger.debug("Confirming it is an escrow transaction...");
if (transaction.attributes.escrowLocation == null) {
logger.error("The transaction is not an escrow transaction...");
errors.put("transaction.is.not.escrowed", transaction);
}
notarizationProvider.throwExceptionOnErrors("invalid.escrow.transaction", errors);
logger.debug("Notarizing the transaction using the escrow signing key...");
String documentType = "Receiver Signature";
String document = transaction.receiverSeal.selfSignature;
transaction.escrowSeal = notarizationProvider.notarizeDocument(documentType, document, escrowKey);
logger.exit(transaction);
}
@Override
public void validateTransaction(DigitalTransaction transaction, DigitalToken token, NotaryCertificate guarantorCertificate, NotaryCertificate accountantCertificate, NotaryCertificate senderCertificate, NotaryCertificate receiverCertificate, Map errors) {
logger.entry(transaction, token, guarantorCertificate, accountantCertificate, senderCertificate, receiverCertificate, errors);
logger.debug("Validating the transaction attributes...");
validateTransactionAttributes(transaction, token, errors);
logger.debug("Validating the token attributes...");
URI batchLocation = token.attributes.batchLocation;
tokenizationProvider.validateToken(token, batchLocation, guarantorCertificate, accountantCertificate, errors);
logger.debug("Validating the sender seal using the sender verification key...");
validateSenderSeal(transaction, senderCertificate, errors);
logger.debug("Validating the receiver seal using the receiver verification key...");
validateReceiverSeal(transaction, receiverCertificate, errors);
logger.debug("Verifying that the escrow location and seal are not set...");
validateNoEscrowSeal(transaction, errors);
logger.exit();
}
@Override
public void validateTransaction(DigitalTransaction transaction, DigitalToken token, NotaryCertificate guarantorCertificate, NotaryCertificate accountantCertificate, NotaryCertificate senderCertificate, NotaryCertificate receiverCertificate, NotaryCertificate escrowCertificate, Map errors) {
logger.entry(transaction, token, guarantorCertificate, accountantCertificate, senderCertificate, receiverCertificate, escrowCertificate, errors);
logger.debug("Validating the transaction attributes...");
validateTransactionAttributes(transaction, token, errors);
logger.debug("Validating the token attributes...");
URI batchLocation = token.attributes.batchLocation;
tokenizationProvider.validateToken(token, batchLocation, guarantorCertificate, accountantCertificate, errors);
logger.debug("Validating the sender seal using the sender verification key...");
validateSenderSeal(transaction, senderCertificate, errors);
logger.debug("Validating the receiver seal using the receiver verification key...");
validateReceiverSeal(transaction, receiverCertificate, errors);
logger.debug("Validating the escrow seal using the escrow verification key...");
validateEscrowSeal(transaction, escrowCertificate, errors);
logger.exit();
}
@Override
public DigitalLedger createLedger(URI ledgerLocation, String channelType, URI channelLocation, NotaryKey accountantKey) {
logger.entry(ledgerLocation, channelType, channelLocation, accountantKey);
DigitalLedger ledger = createLedger(ledgerLocation, channelType, channelLocation, null, accountantKey);
logger.exit(ledger);
return ledger;
}
@Override
public DigitalLedger createLedger(URI ledgerLocation, String channelType, URI channelLocation, Map additionalAttributes, NotaryKey accountantKey) {
logger.entry(ledgerLocation, channelType, channelLocation, additionalAttributes, accountantKey);
logger.debug("Creating the new ledger attributes...");
LedgerAttributes attributes = new LedgerAttributes();
attributes.myLocation = ledgerLocation;
attributes.channelType = channelType;
attributes.channelLocation = channelLocation;
attributes.watermark = notarizationProvider.generateWatermark(Notarization.VALID_FOR_FOREVER);
if (additionalAttributes != null) {
logger.debug("Adding additional attributes...");
for (Map.Entry attribute : additionalAttributes.entrySet()) {
attributes.put(attribute.getKey(), attribute.getKey());
}
}
logger.debug("Validating the ledger attributes...");
DigitalLedger ledger = new DigitalLedger();
ledger.attributes = attributes;
Map errors = new LinkedHashMap<>();
validateLedgerAttributes(ledger, errors);
notarizationProvider.throwExceptionOnErrors("invalid.ledger.attributes", errors);
logger.debug("Notarizing the new ledger using the accountant signing key...");
String documentType = "Ledger Attributes";
String document = attributes.toString();
ledger.accountantSeal = notarizationProvider.notarizeDocument(documentType, document, accountantKey);
logger.exit(ledger);
return ledger;
}
@Override
public LedgerEntry createLedgerEntry(URI entryLocation, DigitalLedger ledger, DigitalTransaction transaction, NotaryKey accountantKey) {
logger.entry(entryLocation, ledger, transaction, accountantKey);
LedgerEntry entry = createLedgerEntry(entryLocation, ledger, transaction, null, null, accountantKey);
logger.exit(entry);
return entry;
}
@Override
public LedgerEntry createLedgerEntry(URI entryLocation, DigitalLedger ledger, DigitalTransaction transaction, Map additionalAttributes, NotaryKey accountantKey) {
logger.entry(entryLocation, ledger, transaction, additionalAttributes, accountantKey);
LedgerEntry entry = createLedgerEntry(entryLocation, ledger, transaction, null, additionalAttributes, accountantKey);
logger.exit(entry);
return entry;
}
@Override
public LedgerEntry createLedgerEntry(URI entryLocation, DigitalLedger ledger, DigitalTransaction transaction, LedgerEntry previousEntry, NotaryKey accountantKey) {
logger.entry(entryLocation, ledger, transaction, previousEntry, accountantKey);
LedgerEntry entry = createLedgerEntry(entryLocation, ledger, transaction, previousEntry, null, accountantKey);
logger.exit(entry);
return entry;
}
@Override
public LedgerEntry createLedgerEntry(URI entryLocation, DigitalLedger ledger, DigitalTransaction transaction, LedgerEntry previousEntry, Map additionalAttributes, NotaryKey accountantKey) {
logger.entry(entryLocation, ledger, transaction, previousEntry, additionalAttributes, accountantKey);
logger.debug("Creating the new entry attributes...");
EntryAttributes attributes = new EntryAttributes();
attributes.myLocation = entryLocation;
attributes.ledgerLocation = ledger.attributes.myLocation;
URI documentLocation = transaction.attributes.myLocation;
String document = transaction.toString();
attributes.transactionCitation = notarizationProvider.generateDocumentCitation(documentLocation, document);
if (previousEntry != null) {
documentLocation = previousEntry.attributes.myLocation;
document = previousEntry.toString();
attributes.previousCitation = notarizationProvider.generateDocumentCitation(documentLocation, document);
attributes.sequenceNumber = previousEntry.attributes.sequenceNumber + 1;
} else {
documentLocation = ledger.attributes.myLocation;
document = ledger.toString();
attributes.previousCitation = notarizationProvider.generateDocumentCitation(documentLocation, document);
attributes.sequenceNumber = 1; // first entry in the ledger
}
attributes.watermark = notarizationProvider.generateWatermark(Notarization.VALID_FOR_FOREVER);
if (additionalAttributes != null) {
logger.debug("Adding additional attributes...");
for (Map.Entry attribute : additionalAttributes.entrySet()) {
attributes.put(attribute.getKey(), attribute.getValue());
}
}
logger.debug("Validating the entry attributes...");
LedgerEntry entry = new LedgerEntry();
entry.attributes = attributes;
Map errors = new LinkedHashMap<>();
validateEntryAttributes(ledger, previousEntry, entry, transaction, errors);
notarizationProvider.throwExceptionOnErrors("invalid.entry.attributes", errors);
logger.debug("Notarizing the new entry using the accountant signing key...");
String documentType = "Ledger Entry Attributes";
document = attributes.toString();
entry.accountantSeal = notarizationProvider.notarizeDocument(documentType, document, accountantKey);
logger.exit(entry);
return entry;
}
@Override
public void validateLedger(DigitalLedger ledger, List entries, List transactions, NotaryCertificate accountantCertificate, Map errors) {
logger.entry(ledger, entries, transactions, accountantCertificate, errors);
logger.debug("Validating the ledger attributes...");
validateLedgerAttributes(ledger, errors);
logger.debug("Validating the accountant seal using the accountant verification key...");
validateAccountantSeal(ledger, accountantCertificate, errors);
logger.debug("Verifying the number of transactions...");
int count = entries.size();
if (count != transactions.size()) {
logger.error("The number of transactions is different than entries...");
errors.put("entries.and.transactions.count.are.different", count);
errors.put("entries.and.transactions.count.are.different", transactions.size());
}
logger.debug("Verifying each ledger entry...");
LedgerEntry previousEntry = null;
for (int i = 0; i < count; i++) {
LedgerEntry entry = entries.get(i);
DigitalTransaction transaction = transactions.get(i);
validateEntryAttributes(ledger, previousEntry, entry, transaction, errors);
validateAccountantSeal(entry, accountantCertificate, errors);
previousEntry = entry;
}
logger.exit();
}
private void validateTransactionAttributes(DigitalTransaction transaction, DigitalToken token, Map errors) {
TransactionAttributes attributes = transaction.attributes;
if (attributes == null) {
logger.error("The transaction attributes are missing...");
errors.put("transaction.attributes.are.missing", transaction);
} else {
if (attributes.myLocation == null) {
logger.error("The transaction location is missing...");
errors.put("transaction.location.is.missing", transaction);
}
if (attributes.senderLocation == null) {
logger.error("The transaction sender location is missing...");
errors.put("sender.location.is.missing", transaction);
}
if (attributes.receiverLocation == null) {
logger.error("The transaction receiver location is missing...");
errors.put("receiver.location.is.missing", transaction);
}
if (attributes.escrowLocation == null && transaction.escrowSeal != null) {
logger.error("The transaction escrow location is missing...");
errors.put("escrow.location.is.missing", transaction);
}
if (attributes.tokenCitation == null) {
logger.error("The transaction token citation is missing...");
errors.put("token.citation.is.missing", transaction);
} else {
notarizationProvider.validateDocumentCitation(attributes.tokenCitation, token.toString(), errors);
}
if (attributes.transactionType == null || attributes.transactionType.isEmpty()) {
logger.error("The transaction type location is missing...");
errors.put("transaction.type.is.missing", transaction);
}
notarizationProvider.validateWatermark(attributes.watermark, errors);
}
}
private void validateSenderSeal(DigitalTransaction transaction, NotaryCertificate senderCertificate, Map errors) {
NotarySeal senderSeal = transaction.senderSeal;
TransactionAttributes attributes = transaction.attributes;
if (senderSeal == null) {
logger.error("The transaction sender seal is missing...");
errors.put("transaction.sender.seal.is.missing", transaction);
} else if (attributes == null) {
logger.error("The transaction attributes are missing...");
errors.put("transaction.attributes.are.missing", transaction);
} else {
String document = attributes.toString();
notarizationProvider.validateDocument(document, senderSeal, senderCertificate, errors);
}
}
private void validateReceiverSeal(DigitalTransaction transaction, NotaryCertificate receiverCertificate, Map errors) {
NotarySeal senderSeal = transaction.senderSeal;
NotarySeal receiverSeal = transaction.receiverSeal;
if (senderSeal == null) {
logger.error("The transaction sender seal is missing...");
errors.put("transaction.sender.seal.is.missing", transaction);
} else if (receiverSeal == null) {
logger.error("The transaction receiver seal is missing...");
errors.put("transaction.receiver.seal.is.missing", transaction);
} else {
String document = senderSeal.selfSignature;
notarizationProvider.validateDocument(document, receiverSeal, receiverCertificate, errors);
}
}
private void validateEscrowSeal(DigitalTransaction transaction, NotaryCertificate escrowCertificate, Map errors) {
NotarySeal receiverSeal = transaction.receiverSeal;
NotarySeal escrowSeal = transaction.escrowSeal;
if (receiverSeal == null) {
logger.error("The transaction receiver seal is missing...");
errors.put("transaction.receiver.seal.is.missing", transaction);
} else if (escrowSeal == null && transaction.attributes.escrowLocation != null) {
logger.error("The transaction escrow seal is missing...");
errors.put("transaction.escrow.seal.is.missing", transaction);
} else if (escrowSeal != null) {
String document = receiverSeal.selfSignature;
notarizationProvider.validateDocument(document, escrowSeal, escrowCertificate, errors);
}
}
private void validateNoEscrowSeal(DigitalTransaction transaction, Map errors) {
if (transaction.attributes.escrowLocation != null && transaction.escrowSeal != null) {
logger.error("The transaction does not support escrow...");
errors.put("transaction.does.not.support.escrow", transaction);
}
}
private void validateLedgerAttributes(DigitalLedger ledger, Map errors) {
LedgerAttributes attributes = ledger.attributes;
if (attributes == null) {
logger.error("The ledger attributes are missing...");
errors.put("ledger.attributes.are.missing", ledger);
} else {
if (attributes.myLocation == null) {
logger.error("The ledger location is missing...");
errors.put("ledger.location.is.missing", ledger);
}
if (attributes.channelType == null || attributes.channelType.isEmpty()) {
logger.error("The ledger channel type is missing...");
errors.put("ledger.channel.type.is.missing", ledger);
}
if (attributes.channelLocation == null) {
logger.error("The ledger channel location is missing...");
errors.put("ledger.channel.location.is.missing", ledger);
}
notarizationProvider.validateWatermark(attributes.watermark, errors);
}
}
private void validateEntryAttributes(DigitalLedger ledger, LedgerEntry previousEntry, LedgerEntry entry, DigitalTransaction transaction, Map errors) {
EntryAttributes attributes = entry.attributes;
if (attributes == null) {
logger.error("The ledger entry attributes are missing...");
errors.put("ledger.entry.attributes.are.missing", entry);
} else {
if (attributes.myLocation == null) {
logger.error("The ledger entry location is missing...");
errors.put("ledger.entry.location.is.missing", entry);
}
if (attributes.ledgerLocation == null) {
logger.error("The ledger entry ledger location is missing...");
errors.put("ledger.entry.ledger.location.is.missing", entry);
}
if (attributes.sequenceNumber < 1) {
logger.error("The ledger entry sequence number is invalid...");
errors.put("ledger.entry.sequence.number.is.invalid", entry);
}
if (attributes.transactionCitation == null) {
logger.error("The ledger entry transaction citation is missing...");
errors.put("ledger.entry.transaction.citation.is.missing", entry);
} else {
notarizationProvider.validateDocumentCitation(attributes.transactionCitation, transaction.toString(), errors);
}
if (attributes.previousCitation == null) {
logger.error("The ledger entry previous citation is missing...");
errors.put("ledger.entry.previous.citation.is.missing", entry);
} else {
String document;
if (previousEntry != null) {
document = previousEntry.toString();
} else {
document = ledger.toString();
}
notarizationProvider.validateDocumentCitation(attributes.previousCitation, document, errors);
}
notarizationProvider.validateWatermark(attributes.watermark, errors);
}
}
private void validateAccountantSeal(DigitalLedger ledger, NotaryCertificate accountantCertificate, Map errors) {
NotarySeal accountantSeal = ledger.accountantSeal;
LedgerAttributes attributes = ledger.attributes;
if (accountantSeal == null) {
logger.error("The ledger accountant seal is missing...");
errors.put("ledger.accountant.seal.is.missing", ledger);
} else if (attributes == null) {
logger.error("The ledger attributes are missing...");
errors.put("ledger.attributes.are.missing", ledger);
} else {
String document = attributes.toString();
notarizationProvider.validateDocument(document, accountantSeal, accountantCertificate, errors);
}
}
private void validateAccountantSeal(LedgerEntry entry, NotaryCertificate accountantCertificate, Map errors) {
NotarySeal accountantSeal = entry.accountantSeal;
EntryAttributes attributes = entry.attributes;
if (accountantSeal == null) {
logger.error("The ledger entry accountant seal is missing...");
errors.put("ledger.entry.accountant.seal.is.missing", entry);
} else if (attributes == null) {
logger.error("The ledger entry attributes are missing...");
errors.put("ledger.entry.attributes.are.missing", entry);
} else {
String document = attributes.toString();
notarizationProvider.validateDocument(document, accountantSeal, accountantCertificate, errors);
}
}
}