package com.ripple.core.types.shamap; import com.ripple.core.coretypes.STObject; import com.ripple.core.coretypes.Vector256; import com.ripple.core.coretypes.hash.Hash256; import com.ripple.core.coretypes.hash.Index; import com.ripple.core.coretypes.uint.UInt32; import com.ripple.core.serialized.enums.LedgerEntryType; import com.ripple.core.types.known.sle.LedgerEntry; import com.ripple.core.types.known.sle.LedgerHashes; import com.ripple.core.types.known.sle.entries.DirectoryNode; import com.ripple.core.types.known.sle.entries.OfferDirectory; import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONTokener; import org.json.JSONWriter; import java.io.FileReader; import java.io.IOException; import java.util.Iterator; public class AccountState extends ShaMap { public AccountState() { super(); } public AccountState(boolean isCopy, int depth) { super(isCopy, depth); } @Override protected ShaMapInner makeInnerOfSameClass(int depth) { return new AccountState(true, depth); } private static LedgerHashes newSkipList(Hash256 skipIndex) { LedgerHashes skip; skip = new LedgerHashes(); skip.put(UInt32.Flags, new UInt32(0)); skip.hashes(new Vector256()); skip.index(skipIndex); return skip; } public void updateSkipLists(long currentIndex, Hash256 parentHash) { long prev = currentIndex - 1; if ((prev & 0xFF) == 0) { Hash256 skipIndex = Index.ledgerHashes(prev); LedgerHashes skip = createOrUpdateSkipList(skipIndex); Vector256 hashes = skip.hashes(); assert hashes.size() <= 256; hashes.add(parentHash); skip.put(UInt32.LastLedgerSequence, new UInt32(prev)); } Hash256 skipIndex = Index.ledgerHashes(); LedgerHashes skip = createOrUpdateSkipList(skipIndex); Vector256 hashes = skip.hashes(); if (hashes.size() > 256) throw new AssertionError(); if (hashes.size() == 256) { hashes.remove(0); } hashes.add(parentHash); skip.put(UInt32.LastLedgerSequence, new UInt32(prev)); } private LedgerHashes createOrUpdateSkipList(Hash256 skipIndex) { PathToIndex path = pathToIndex(skipIndex); ShaMapInner top = path.dirtyOrCopyInners(); LedgerEntryItem item; if (path.hasMatchedLeaf()) { ShaMapLeaf leaf = path.invalidatedPossiblyCopiedLeafForUpdating(); item = (LedgerEntryItem) leaf.item; } else { item = new LedgerEntryItem(newSkipList(skipIndex)); top.addLeafToTerminalInner(new ShaMapLeaf(skipIndex, item)); } return (LedgerHashes) item.entry; } public boolean addLE(LedgerEntry entry) { LedgerEntryItem item = new LedgerEntryItem(entry); return addItem(entry.index(), item); } public boolean updateLE(LedgerEntry entry) { LedgerEntryItem item = new LedgerEntryItem(entry); return updateItem(entry.index(), item); } public LedgerEntry getLE(Hash256 index) { LedgerEntryItem item = (LedgerEntryItem) getItem(index); return item == null ? null : item.value(); } public DirectoryNode getDirectoryNode(Hash256 index) { return (DirectoryNode) getLE(index); } public Iterable<OfferDirectory> offerDirectories(Hash256 bookBase) { final QualityIterator iter = qualityIterator(bookBase); return new Iterable<OfferDirectory>() { @Override public Iterator<OfferDirectory> iterator() { return new Iterator<OfferDirectory>() { @Override public boolean hasNext() { boolean hasNext = iter.hasNext(); return hasNext && nextEntry().ledgerEntryType() == LedgerEntryType.DirectoryNode; } private LedgerEntry nextEntry() { return ((LedgerEntryItem) iter.next.item).entry; } @Override public OfferDirectory next() { return (OfferDirectory) iter.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } public void writeEntriesArray(final JSONWriter writer) { writer.array(); walkEntries(new LedgerEntryVisitor() { @Override public void onEntry(LedgerEntry entry) { writer.value(entry.toJSON()); } }); writer.endArray(); } // Assumes shamap won't be modified during iteration, not unusual for an // iterator. public class QualityIterator implements Iterator<LedgerEntry> { ShaMapInner[] inners = new ShaMapInner[64]; Hash256 base; Hash256 end; int[] selections = new int[64]; int commonNibblets; int depth; ShaMapLeaf next; private boolean finished = false; public QualityIterator(Hash256 start) { base = start; depth=0; end = Index.bookEnd(start); ShaMapInner inner = AccountState.this; setInner(inner); setSelected(start.nibblet(depth)); findCommonNibblets(); } private void findCommonNibblets() { for (int i = 0; i < 64; i++) { if (base.nibblet(i) == end.nibblet(i)) { commonNibblets = i; } else { break; } } } private void setInner(ShaMapInner inner) { inners[depth] = inner; } private void findNext() { next = null; while (true) { while (selected() > 15) { depth--; incrementSelection(); } if (depth < commonNibblets && selected() > end.nibblet(depth)) { finished = true; break; } ShaMapInner current = currentInner(); ShaMapNode branch = current.getBranch(selected()); if (branch == null) { incrementSelection(); } else if (branch.isInner()) { depth++; setInner(branch.asInner()); setSelected(base.nibblet(depth)); } else if (branch.isLeaf()) { ShaMapLeaf leaf = branch.asLeaf(); Hash256 leafIndex = leaf.index; boolean leafIsOnPathToBase = depth < commonNibblets; if ( leafIsOnPathToBase || leafIndex.compareTo(base) > 0 && leafIndex.compareTo(end) < 0) { next = leaf; incrementSelection(); } else { finished = true; } break; } } } private ShaMapInner currentInner() { return inners[depth]; } private int selected() { return selections[depth]; } private void setSelected(int nibblet) { selections[depth] = nibblet; } private void incrementSelection() { selections[depth]++; } @Override public boolean hasNext() { findNext(); return !finished && next != null; } @Override public LedgerEntry next() { // Just assume hasNext has been called LedgerEntryItem item = (LedgerEntryItem) next.item; return item.entry; } @Override public void remove() { throw new UnsupportedOperationException(); } } private QualityIterator qualityIterator(final Hash256 bookBase) { return new QualityIterator(bookBase); } public Iterable<Hash256> directoryIterator(OfferDirectory forQuality) { // TODO: create an actual iterator Vector256 indexes = new Vector256(); OfferDirectory cursor = forQuality; while (cursor != null) { indexes.addAll(cursor.indexes()); if (cursor.hasNextIndex()) { LedgerEntry le = getLE(cursor.nextIndex()); if (le instanceof OfferDirectory) { cursor = (OfferDirectory) le; } else { break; } } else { break; } } return indexes; } public void walkEntries(final LedgerEntryVisitor walker) { walkLeaves(new LeafWalker() { @Override public void onLeaf(ShaMapLeaf leaf) { LedgerEntryItem item = (LedgerEntryItem) leaf.item; walker.onEntry(item.entry); } }); } // TODO public Hash256 getNextIndex(Hash256 nextIndex, Hash256 bookEnd) { return null; } @Override public AccountState copy() { return (AccountState) super.copy(); } public static AccountState loadFromLedgerDump(String filePath) throws IOException { FileReader reader = new FileReader(filePath); JSONTokener tokenizer = new JSONTokener(reader); JSONObject ledger = new JSONObject(tokenizer); if (ledger.has("result")) { ledger = ledger.getJSONObject("result"); } if (ledger.has("ledger")) { ledger = ledger.getJSONObject("ledger"); } JSONArray array = ledger.getJSONArray("accountState"); reader.close(); return parseShaMap(array); } public static AccountState parseShaMap(JSONArray array) { AccountState map = new AccountState(); for (int i = 0; i < array.length(); i++) { JSONObject jsonItem = array.getJSONObject(i); map.addLE((LedgerEntry) STObject.fromJSONObject(jsonItem)); } return map; } }