Skip to content

Commit

Permalink
More tests and infrastructure for state recalculation
Browse files Browse the repository at this point in the history
  • Loading branch information
mikera committed Feb 4, 2025
1 parent ebbf016 commit 2595557
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 6 deletions.
5 changes: 4 additions & 1 deletion convex-core/src/main/java/convex/core/cvm/Keywords.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public class Keywords {
public static final Keyword PERSIST = Keyword.intern("persist");
public static final Keyword POLL_DELAY = Keyword.intern("poll-delay");


// configuration parameters
public static final Keyword STORE = Keyword.intern("store");
public static final Keyword RESTORE = Keyword.intern("restore");
public static final Keyword RECALC = Keyword.intern("recalc");

// for testing and suchlike
public static final Keyword FOO = Keyword.intern("foo");
Expand Down Expand Up @@ -148,6 +149,8 @@ public class Keywords {

// Commond trust keys
public static final Keyword CONTROL = Keyword.intern("control");





Expand Down
31 changes: 31 additions & 0 deletions convex-core/src/main/java/convex/core/cvm/Peer.java
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,31 @@ public Peer updateState() {
}
return new Peer(keyPair, belief, myOrder,stateIndex,s, genesis, historyPosition,newResults, timestamp);
}

public Peer recalcState(long pos) {
Peer result=truncateState(pos);
result=result.updateState();
return result;
}

public Peer truncateState(long pos) {
if (pos>=statePosition) return this;

AVector<BlockResult> newResults=blockResults;
State newState=state;
long newHistory=historyPosition;
if (pos>historyPosition) {
// within existing history
newState=blockResults.get(pos-historyPosition-1).getState();
newResults=newResults.slice(0, pos-historyPosition);
} else {
// recalculate from beginning
newResults=Vectors.empty();
newState=genesis;
pos=0;
}
return new Peer(keyPair, belief, consensusOrder, pos, newState, genesis, newHistory, newResults, timestamp);
}

private void validateSignatures(State s, AVector<SignedData<Block>> blocks, long start, long end) {
Consumer<SignedData<ATransaction>> transactionValidator=st->{
Expand Down Expand Up @@ -721,4 +746,10 @@ public boolean isReadyToPublish() {
public Hash getGenesisHash() {
return getGenesisState().getHash();
}

public long getHistoryPosition() {
return historyPosition;
}


}
47 changes: 46 additions & 1 deletion convex-core/src/main/java/convex/core/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import convex.core.data.Blob;
import convex.core.data.Hash;
import convex.core.data.impl.ALongBlob;
import convex.core.data.prim.AInteger;
import convex.core.lang.RT;

/**
Expand Down Expand Up @@ -651,7 +652,18 @@ public static int byteLength(BigInteger bi) {
public static int toInt(Object v) {
if (v instanceof Integer) return (Integer) v;
if (v instanceof String) {
return Integer.parseInt((String) v);
try {
return Integer.parseInt((String) v);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("String cannot be converted to an integer");
}
}
if (v instanceof ACell) {
AInteger cv=AInteger.parse(v);
if (cv==null) throw new IllegalArgumentException("Cell not a integer numeric value: " + v);
Integer result=(int)cv.longValue();
if (result.longValue()==cv.doubleValue()) return result;
throw new IllegalArgumentException("CVM numeric value not in Java Integer range");
}
if (v instanceof Number) {
Number number = (Number) v;
Expand All @@ -662,6 +674,38 @@ public static int toInt(Object v) {
}
throw new IllegalArgumentException("Can't convert to int: " + v);
}

/**
* Converts an object to a Long value, handling Strings and arbitrary numbers.
*
* @param v An object representing a valid int value
* @return The converted int value of the object
* @throws IllegalArgumentException If the argument cannot be converted to an
* int
*/
public static long toLong(Object v) {
if (v instanceof Long) return (Integer) v;
if (v instanceof String) {
try {
return Long.parseLong((String) v);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("String cannot be converted to a Long");
}
}
if (v instanceof ACell) {
AInteger cv=AInteger.parse(v);
if (cv==null) throw new IllegalArgumentException("Cell not a integer numeric value: " + v);
return cv.longValue();
}
if (v instanceof Number) {
Number number = (Number) v;
long value = number.longValue();
// following is safe, because double can represent any int
if (value != number.doubleValue()) throw new IllegalArgumentException("Cannot coerce to long without loss:");
return value;
}
throw new IllegalArgumentException("Can't convert to int: " + v);
}

/**
* Gets a resource as a String.
Expand Down Expand Up @@ -1377,4 +1421,5 @@ private static String timeString(Instant timeStamp) {




}
76 changes: 75 additions & 1 deletion convex-core/src/test/java/convex/core/cvm/PeerTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package convex.core.cvm;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -13,14 +15,18 @@
import org.junit.jupiter.api.TestInstance.Lifecycle;

import convex.core.crypto.AKeyPair;
import convex.core.cvm.transactions.Invoke;
import convex.core.data.ACell;
import convex.core.data.AMap;
import convex.core.data.AccountKey;
import convex.core.data.Keyword;
import convex.core.data.ObjectsTest;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.InvalidDataException;
import convex.core.init.Init;
import convex.core.util.FileUtils;

import convex.core.util.Utils;
import convex.core.cpos.Block;
import convex.core.cpos.CPoSConstants;

@TestInstance(Lifecycle.PER_CLASS)
Expand All @@ -36,6 +42,7 @@ public class PeerTest {
@Test
public void testBasicPeer() throws IOException, BadFormatException {
Peer p=Peer.create(KP, GENESIS);
doPeerTest(p);

AMap<Keyword, ACell> data = p.toData();
assertNotNull(data);
Expand All @@ -59,9 +66,76 @@ public void testBasicPeer() throws IOException, BadFormatException {
assertEquals(p.getBelief(),p4.getBelief());
}

@Test
public void testPeerUpdates() throws InvalidDataException {
Peer p=Peer.create(KP, GENESIS);
long ts=p.getTimestamp();

for (int i=0; i<10; i++) {
Block b=Block.of(ts+i, KP.signData(Invoke.create(Init.GENESIS_ADDRESS, i+1, "(* "+i+" "+i+")")));
p=p.proposeBlock(b);
}
assertEquals(ts+9,p.getTimestamp());
assertEquals(0,p.getStatePosition());
assertEquals(0,p.getFinalityPoint());
assertEquals(10,p.getPeerOrder().getBlockCount());
p=p.mergeBeliefs();
p=p.mergeBeliefs();
p=p.mergeBeliefs();
p=p.mergeBeliefs();
p=p.updateState();
assertEquals(10,p.getStatePosition());
assertEquals(81,Utils.toInt(p.getBlockResult(9).getResults().get(0).getValue()));

// check all invariants
doPeerTest(p);

{ // Truncate to genesis
Peer pt=p.truncateState(0);
assertEquals(0,pt.getStatePosition());
assertEquals(GENESIS,pt.getConsensusState());
doPeerTest(pt);

// replay transactions up to consensus
pt=pt.updateState();
assertEquals(p.getConsensusState(),pt.getConsensusState());

assertEquals(pt.getBlockResult(7),p.getBlockResult(7));
}

{ // Truncate to mid point
Peer pt=p.truncateState(5);
assertEquals(5,pt.getStatePosition());
assertNotEquals(GENESIS,pt.getConsensusState());
doPeerTest(pt);

// replay transactions up to consensus
pt=pt.updateState();
assertEquals(p.getConsensusState(),pt.getConsensusState());

assertEquals(pt.getBlockResult(7),p.getBlockResult(7));

}

{ // Truncate to distant future
Peer pt=p.truncateState(Long.MAX_VALUE);
assertEquals(p.getStatePosition(),pt.getStatePosition());
doPeerTest(pt);
}
}

private void doPeerTest(Peer pt) {
assertTrue(pt.getGenesisHash().equals(GENESIS.getHash()));

long sp=pt.getStatePosition();
long hp=pt.getHistoryPosition();
assertTrue(sp>=hp);
}

@Test
public void testPeerStatus() {
PeerStatus ps=PeerStatus.create(Address.ZERO, CPoSConstants.MINIMUM_EFFECTIVE_STAKE);

ObjectsTest.doCellTests(ps);
}
}
23 changes: 23 additions & 0 deletions convex-core/src/test/java/convex/util/UtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,29 @@ public void testToInt() {
assertEquals(7, Utils.toInt(7.0f));
assertEquals(8, Utils.toInt("8"));
assertEquals(-1, Utils.toInt("-1"));
assertEquals(Integer.MAX_VALUE, Utils.toInt("2147483647"));
assertEquals(Integer.MIN_VALUE, Utils.toInt(CVMLong.parse("-2147483648")));

assertThrows(IllegalArgumentException.class, ()->Utils.toInt("foo"));
assertThrows(IllegalArgumentException.class, ()->Utils.toInt(1.5));
assertThrows(IllegalArgumentException.class, ()->Utils.toInt(null));
}

@Test
public void testToLong() {
assertEquals(1, Utils.toLong(1));
assertEquals(7, Utils.toLong(7.0f));
assertEquals(8, Utils.toLong("8"));
assertEquals(-1, Utils.toLong("-1"));
assertEquals(Integer.MAX_VALUE, Utils.toLong("2147483647"));
assertEquals(Integer.MIN_VALUE, Utils.toLong(CVMLong.parse("-2147483648")));

assertEquals(Long.MAX_VALUE, Utils.toLong("9223372036854775807"));

assertThrows(IllegalArgumentException.class, ()->Utils.toLong("foo"));
assertThrows(IllegalArgumentException.class, ()->Utils.toLong(1.5));
assertThrows(IllegalArgumentException.class, ()->Utils.toLong(null));

}

@Test
Expand Down
10 changes: 9 additions & 1 deletion convex-peer/src/main/java/convex/peer/CVMExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import convex.core.cpos.Belief;
import convex.core.cvm.Peer;
import convex.core.exceptions.TODOException;
import convex.core.util.LatestUpdateQueue;
import convex.core.util.LoadMonitor;

Expand Down Expand Up @@ -61,7 +62,12 @@ protected void loop() throws InterruptedException {

public void syncPeer(Server base) {
// TODO Auto-generated method stub

throw new TODOException();
}

public synchronized void recalcState(long pos) {
// TODO Auto-generated method stub
peer=peer.recalcState(pos);
}

public synchronized void persistPeerData() throws IOException {
Expand Down Expand Up @@ -99,4 +105,6 @@ public void setUpdateHook(Consumer<Peer> hook) {





}
13 changes: 11 additions & 2 deletions convex-peer/src/main/java/convex/peer/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ public synchronized void launch() throws LaunchException, InterruptedException {
executor.persistPeerData();

HashMap<Keyword, Object> config = getConfig();

if (config.containsKey(Keywords.RECALC)) try {
Long pos=Utils.toLong(config.get(Keywords.RECALC));
executor.recalcState(pos);
} catch (Exception e) {
throw new LaunchException("Launch failed to recalculate state: "+e,e);
}

Object p = config.get(Keywords.PORT);
Integer port = (p == null) ? null : Utils.toInt(p);
Expand All @@ -370,6 +377,8 @@ public synchronized void launch() throws LaunchException, InterruptedException {
// Close server on shutdown, should be before Etch stores in priority
Shutdown.addHook(Shutdown.SERVER, ()->close());



// Start threaded components
manager.start();
queryHandler.start();
Expand All @@ -381,9 +390,9 @@ public synchronized void launch() throws LaunchException, InterruptedException {
goLive();
log.info( "Peer server started on port "+nio.getPort()+" with peer key: {}",getPeerKey());
} catch (ConfigException e) {
throw new LaunchException("Launch failed due to config problem",e);
throw new LaunchException("Launch failed due to config problem: "+e,e);
} catch (IOException e) {
throw new LaunchException("Launch failed due to IO Error",e);
throw new LaunchException("Launch failed due to IO Error: "+e,e);
} finally {
Stores.setCurrent(savedStore);
}
Expand Down

0 comments on commit 2595557

Please sign in to comment.