All Downloads are FREE. Search and download functionalities are using the official Maven repository.

craterdog.accounting.V1AccountingProvider Maven / Gradle / Ivy

Go to download

This project defines a set of interfaces and classes that implement digital accounting.

There is a newer version: 1.6
Show newest version
/************************************************************************
 * 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);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy