Skip to content

Commit

Permalink
Add test for RevertibleOperationStack
Browse files Browse the repository at this point in the history
  • Loading branch information
ben221199 committed Sep 20, 2024
1 parent 7d2ca00 commit f64ec2e
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 31 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/lbry/database/PrefixDB.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public void unsafeCommit() throws RocksDBException{
return;
}
WriteBatch batch = new WriteBatch();
for(RevertibleOperation stagedChange : this.operationStack.interate()){
for(RevertibleOperation stagedChange : this.operationStack.iterate()){
ColumnFamilyHandle columnFamily = this.getColumnFamilyByPrefix(Prefix.getByValue(stagedChange.getKey()[0]));
if(!stagedChange.isDelete()){
batch.put(columnFamily,stagedChange.getKey(),stagedChange.getValue());
Expand Down Expand Up @@ -203,7 +203,7 @@ public void commit(int height,byte[] blockHash) throws RocksDBException{
WriteOptions writeOptions = new WriteOptions().setSync(true);
try{
WriteBatch batch = new WriteBatch();
for(RevertibleOperation stagedChange : this.operationStack.interate()){
for(RevertibleOperation stagedChange : this.operationStack.iterate()){
ColumnFamilyHandle columnFamily = this.getColumnFamilyByPrefix(Prefix.getByValue(stagedChange.getKey()[0]));
if(!stagedChange.isDelete()){
batch.put(columnFamily,stagedChange.getKey(),stagedChange.getValue());
Expand Down Expand Up @@ -242,7 +242,7 @@ public void rollback(int height,byte[] blockHash) throws RocksDBException{
WriteOptions writeOptions = new WriteOptions().setSync(true);
try{
WriteBatch batch = new WriteBatch();
for(RevertibleOperation stagedChange : this.operationStack.interate()){
for(RevertibleOperation stagedChange : this.operationStack.iterate()){
ColumnFamilyHandle columnFamily = this.getColumnFamilyByPrefix(Prefix.getByValue(stagedChange.getKey()[0]));
if(!stagedChange.isDelete()){
batch.put(columnFamily,stagedChange.getKey(),stagedChange.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public byte[] getValue(){
return this.value;
}

public boolean isPut(){
return this.isPut;
}

public boolean isDelete(){
return !this.isPut;
}
Expand Down Expand Up @@ -74,13 +78,13 @@ public boolean equals(Object obj){

@Override
public String toString() {
Prefix prefix = Prefix.getByValue(this.value[0]);
Prefix prefix = Prefix.getByValue(this.key[0]);
String prefixStr = (prefix!=null?prefix.name():"?");
String k = "?";
String v = "?";
if(prefix!=null){
k = PrefixRow.TYPES.get(prefix).unpackKey(this.key).toString();
v = PrefixRow.TYPES.get(prefix).unpackKey(this.value).toString();
v = PrefixRow.TYPES.get(prefix).unpackValue(this.value).toString();
}
return (this.isPut?"PUT":"DELETE")+" "+prefixStr+": "+k+" | "+v;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.lbry.database.revert;

import com.lbry.database.util.MapHelper;
import com.lbry.database.util.Tuple2;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Function;
Expand All @@ -22,6 +25,14 @@ public class RevertibleOperationStack{

private final boolean enforceIntegrity;

public RevertibleOperationStack(Function<byte[],Optional<byte[]>> get,Function<List<byte[]>,Iterable<Optional<byte[]>>> multiGet){
this(get,multiGet,null);
}

public RevertibleOperationStack(Function<byte[],Optional<byte[]>> get,Function<List<byte[]>,Iterable<Optional<byte[]>>> multiGet,Set<Byte> unsafePrefixes){
this(get,multiGet,unsafePrefixes,true);
}

public RevertibleOperationStack(Function<byte[],Optional<byte[]>> get,Function<List<byte[]>,Iterable<Optional<byte[]>>> multiGet,Set<Byte> unsafePrefixes,boolean enforceIntegrity){
this.get = get;
this.multiGet = multiGet;
Expand Down Expand Up @@ -179,18 +190,23 @@ public void validateAndApplyStashedOperations(){
this.stashedLastOperationForKey.clear();
}

/**
* Apply a put or delete op, checking that it introduces no integrity errors.
* @param operation The revertible operation
*/
public void appendOperation(RevertibleOperation operation){
RevertibleOperation inverted = operation.invert();

RevertibleOperation[] operationArr = null;
for(Map.Entry<byte[],RevertibleOperation[]> e : this.items.entrySet()){
if(Arrays.equals(e.getKey(),operation.getKey())){
operationArr = e.getValue();
}
}
RevertibleOperation[] operationArr = MapHelper.getValue(this.items,operation.getKey());
if(operationArr!=null && operationArr.length>=1 && inverted.equals(operationArr[operationArr.length-1])){
// If the new op is the inverse of the last op, we can safely null both.
this.items.put(operationArr[0].getKey(),Arrays.copyOfRange(operationArr,0,operationArr.length-1));
return;
}else if(operationArr!=null && operationArr.length>=1 && operationArr[operationArr.length-1].equals(operation)){
// Duplicate of last operation.
return; // Raise an error?
}

Optional<byte[]> storedValue = this.get.apply(operation.getKey());
boolean hasStoredValue = storedValue.isPresent();
RevertibleOperation deleteStoredOperation = hasStoredValue?new RevertibleDelete(operation.getKey(),storedValue.get()):null;
Expand Down Expand Up @@ -231,7 +247,10 @@ public void appendOperation(RevertibleOperation operation){
operationArrX = e.getValue();
}
}
RevertibleOperation[] newArr = new RevertibleOperation[operationArrX==null?0:operationArrX.length];
RevertibleOperation[] newArr = new RevertibleOperation[operationArrX==null?1:operationArrX.length+1];
if(operationArrX!=null){
System.arraycopy(operationArrX,0,newArr,0,operationArrX.length);
}
newArr[newArr.length-1] = operation;
this.items.put(newArr[0].getKey(),newArr);
}
Expand Down Expand Up @@ -424,7 +443,7 @@ public int length(){
return this.items.values().stream().mapToInt(x -> x.length).sum();
}

public Iterable<RevertibleOperation> interate(){
public Iterable<RevertibleOperation> iterate(){
return this.items.values().stream().flatMap(Stream::of).collect(Collectors.toList());
}

Expand All @@ -433,23 +452,21 @@ public Iterable<RevertibleOperation> interate(){
*/
public byte[] getUndoOperations(){
List<RevertibleOperation> reversed = new ArrayList<>();
for(Map.Entry<byte[],RevertibleOperation[]> e : this.items.entrySet()){
List<RevertibleOperation> operations = Arrays.asList(e.getValue());
Collections.reverse(operations);
reversed.addAll(operations);
for(RevertibleOperation operation : this.iterate()){
reversed.add(operation);
}
List<byte[]> invertedAndPacked = new ArrayList<>();
int size = 0;
Collections.reverse(reversed);

ByteArrayOutputStream baos = new ByteArrayOutputStream();

for(RevertibleOperation operation : reversed){
byte[] undoOperation = operation.invert().pack();
invertedAndPacked.add(undoOperation);
size += undoOperation.length;
}
ByteBuffer bb = ByteBuffer.allocate(size);
for(byte[] packed : invertedAndPacked){
bb.put(packed);
try{
baos.write(operation.invert().pack());
}catch(IOException e){
e.printStackTrace();
}
}
return bb.array();
return baos.toByteArray();
}

/**
Expand All @@ -459,7 +476,9 @@ public byte[] getUndoOperations(){
public void applyPackedUndoOperations(byte[] packed){
while(packed.length>0){
Tuple2<RevertibleOperation,byte[]> unpacked = RevertibleOperation.unpack(packed);
this.appendOperation(unpacked.getA());
this.stash.add(unpacked.getA());
byte[] savedKey = MapHelper.getKey(this.stashedLastOperationForKey,unpacked.getA().getKey());
this.stashedLastOperationForKey.put(savedKey!=null?savedKey:unpacked.getA().getKey(),unpacked.getA());
packed = unpacked.getB();
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/lbry/database/revert/RevertiblePut.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

public class RevertiblePut extends RevertibleOperation{

protected boolean isPut = true;

public RevertiblePut(byte[] key,byte[] value){
super(key,value);
this.isPut = true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public ClaimToTXOKey unpackKey(byte[] key) {
public byte[] packValue(ClaimToTXOValue value) {
byte[] strBytes = value.name.getBytes();

return ByteBuffer.allocate(4+2+4+2+8+1)
return ByteBuffer.allocate(4+2+4+2+8+1+2+strBytes.length)
.order(ByteOrder.BIG_ENDIAN)
.putInt(value.tx_num)
.putShort(value.position)
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/lbry/database/util/MapHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.lbry.database.util;

import java.util.Arrays;
import java.util.Map;

public class MapHelper{

public static <V> byte[] getKey(Map<byte[],V> map,byte[] key){
for(Map.Entry<byte[],V> entry : map.entrySet()){
if(Arrays.equals(entry.getKey(),key)){
return entry.getKey();
}
}
return null;
}

public static <V> V getValue(Map<byte[],V> map,byte[] key){
byte[] savedKey = MapHelper.getKey(map,key);
if(savedKey!=null){
return map.get(savedKey);
}
return null;
}

public static <V> V remove(Map<byte[],V> map,byte[] key){
byte[] savedKey = MapHelper.getKey(map,key);
if(savedKey!=null){
return map.remove(savedKey);
}
return null;
}

}
Loading

0 comments on commit f64ec2e

Please sign in to comment.