Skip to content

Commit

Permalink
Audit log reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
fireduck64 committed Apr 27, 2019
1 parent d798cf2 commit 92b74b5
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 0 deletions.
137 changes: 137 additions & 0 deletions client/src/AuditLog.java
Expand Up @@ -5,6 +5,10 @@
import snowblossom.util.proto.*;
import com.google.protobuf.ByteString;

import java.util.LinkedList;
import java.util.HashMap;
import java.util.HashSet;

public class AuditLog
{
public static String init(SnowBlossomClient client, String msg)
Expand Down Expand Up @@ -89,5 +93,138 @@ public static String recordLog(SnowBlossomClient client, ByteString msg)

}

public static AuditLogReport getAuditReport(SnowBlossomClient client, AddressSpecHash audit_log_hash)
{
RequestAddress req_addr = RequestAddress.newBuilder().setAddressSpecHash( audit_log_hash.getBytes() ).build();
HistoryList history_list = client.getStub().getAddressHistory( req_addr );
TransactionHashList mempool_list = client.getStub().getMempoolTransactionList(req_addr);

if (history_list.getNotEnabled()) throw new RuntimeException("Node does not support history. Unable to log.");

HashMap<ChainHash, Integer> tx_height_map = new HashMap<>();
LinkedList<ByteString> addr_list = new LinkedList<>();
for( HistoryEntry he : history_list.getEntriesList())
{

tx_height_map.put(new ChainHash(he.getTxHash()), he.getBlockHeight());
addr_list.add(he.getTxHash());
}
for (ByteString tx_hash : mempool_list.getTxHashesList())
{
addr_list.add(tx_hash);
}

HashMap<ChainHash, Transaction> tx_map = new HashMap<>();
HashSet<ChainHash> spent_tx = new HashSet<>();
for(ByteString tx_hash : addr_list)
{
Transaction tx = client.getStub().getTransaction( RequestTransaction.newBuilder().setTxHash(tx_hash).build() );
tx_map.put(new ChainHash(tx_hash), tx);

if (acceptableAuditTransaction(audit_log_hash, tx))
{
TransactionInner tx_inner = TransactionUtil.getInner(tx);
for(TransactionInput input : tx_inner.getInputsList())
{
spent_tx.add( new ChainHash(input.getSrcTxId()));
}
}

}

AuditLogReport.Builder report = AuditLogReport.newBuilder();

for(ChainHash tx_hash : tx_map.keySet())
{
if (!spent_tx.contains(tx_hash))
{
AuditLogChain chain = getAuditChain(audit_log_hash, tx_hash, tx_map, tx_height_map);
if (chain != null)
{
report.addChains(chain);
}
}
}

return report.build();
}

public static AuditLogChain getAuditChain(AddressSpecHash audit_log_hash, ChainHash head,
HashMap<ChainHash, Transaction> tx_map, HashMap<ChainHash, Integer> tx_height_map)
{
AuditLogChain.Builder chain = AuditLogChain.newBuilder();
int chain_len = 0;
ChainHash curr = head;
while(curr != null)
{
Transaction tx = tx_map.get(curr);
if (acceptableAuditTransaction(audit_log_hash, tx_map.get(curr)))
{
chain_len++;
TransactionInner inner = TransactionUtil.getInner(tx);
AuditLogItem.Builder item = AuditLogItem.newBuilder();
item.setTxHash(curr.getBytes());
item.setLogMsg( inner.getExtra() );
if (tx_height_map.containsKey(curr))
{
item.setConfirmedHeight(tx_height_map.get(curr));
}
chain.addItems(item.build());

int acceptable_sources = 0;
ChainHash prev = null;
for(TransactionInput input : inner.getInputsList())
{
if (acceptableAuditTransaction(audit_log_hash, tx_map.get( new ChainHash( input.getSrcTxId() ) ) ))
{
prev = new ChainHash( input.getSrcTxId() );
acceptable_sources++;
}
}
// Only continue if there is exactly one obvious choice
if (acceptable_sources == 1) curr = prev;
else curr = null;

}
else
{
curr = null;
}

}


if (chain_len > 0) return chain.build();
return null;
}

private static boolean acceptableAuditTransaction(AddressSpecHash audit_log_hash, Transaction tx)
{
if (tx == null) return false;
TransactionInner inner = TransactionUtil.getInner(tx);

// Must have exactly one output to log address
int log_outputs = 0;
for(TransactionOutput out : inner.getOutputsList())
{
if (audit_log_hash.equals(out.getRecipientSpecHash())) log_outputs++;
}
if (log_outputs != 1) return false;


// Must be signed by log address
int log_sig = 0;
for(AddressSpec claim : inner.getClaimsList())
{
AddressSpecHash addr = AddressUtil.getHashForSpec(claim);
if (addr.equals(audit_log_hash)) log_sig++;
}

if (log_sig != 1) return false;

return true;

}


}
14 changes: 14 additions & 0 deletions client/src/SnowBlossomClient.java
Expand Up @@ -340,6 +340,19 @@ else if (command.equals("audit_log_record"))
System.out.println(AuditLog.recordLog(client, msg));

}
else if (command.equals("audit_log_report"))
{
if (args.length != 3)
{
System.out.println("Syntax: audit_log_report <address>");
System.exit(-1);
}

AddressSpecHash audit_log_hash = AddressUtil.getHashForAddress(client.getParams().getAddressPrefix(), args[2]);

AuditLogReport report = AuditLog.getAuditReport(client, audit_log_hash);
System.out.println(report);
}
else
{
logger.log(Level.SEVERE, String.format("Unknown command %s.", command));
Expand All @@ -360,6 +373,7 @@ else if (command.equals("audit_log_record"))
System.out.println(" rpcserver - run a local rpc server for client commands");
System.out.println(" audit_log_init <msg> - initialize a new audit log chain");
System.out.println(" audit_log_record <msg> - record next audit log in chain");
System.out.println(" audit_log_report <address> - get a report of audit log on address");

System.exit(-1);
}
Expand Down
15 changes: 15 additions & 0 deletions protolib/util.proto
Expand Up @@ -51,3 +51,18 @@ message TransactionFactoryResult {
int64 fee = 3;
int32 signatures_added = 4;
}

message AuditLogReport {
repeated AuditLogChain chains = 1;
}

message AuditLogChain {
repeated AuditLogItem items = 1;
}

message AuditLogItem {
bytes tx_hash = 1;
int32 confirmed_height = 2;
bytes log_msg = 3;
}

0 comments on commit 92b74b5

Please sign in to comment.