Skip to content

Commit 2595557

Browse files
committed
More tests and infrastructure for state recalculation
1 parent ebbf016 commit 2595557

File tree

7 files changed

+199
-6
lines changed

7 files changed

+199
-6
lines changed

convex-core/src/main/java/convex/core/cvm/Keywords.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ public class Keywords {
2121
public static final Keyword PERSIST = Keyword.intern("persist");
2222
public static final Keyword POLL_DELAY = Keyword.intern("poll-delay");
2323

24-
24+
// configuration parameters
2525
public static final Keyword STORE = Keyword.intern("store");
2626
public static final Keyword RESTORE = Keyword.intern("restore");
27+
public static final Keyword RECALC = Keyword.intern("recalc");
2728

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

149150
// Commond trust keys
150151
public static final Keyword CONTROL = Keyword.intern("control");
152+
153+
151154

152155

153156

convex-core/src/main/java/convex/core/cvm/Peer.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,31 @@ public Peer updateState() {
520520
}
521521
return new Peer(keyPair, belief, myOrder,stateIndex,s, genesis, historyPosition,newResults, timestamp);
522522
}
523+
524+
public Peer recalcState(long pos) {
525+
Peer result=truncateState(pos);
526+
result=result.updateState();
527+
return result;
528+
}
529+
530+
public Peer truncateState(long pos) {
531+
if (pos>=statePosition) return this;
532+
533+
AVector<BlockResult> newResults=blockResults;
534+
State newState=state;
535+
long newHistory=historyPosition;
536+
if (pos>historyPosition) {
537+
// within existing history
538+
newState=blockResults.get(pos-historyPosition-1).getState();
539+
newResults=newResults.slice(0, pos-historyPosition);
540+
} else {
541+
// recalculate from beginning
542+
newResults=Vectors.empty();
543+
newState=genesis;
544+
pos=0;
545+
}
546+
return new Peer(keyPair, belief, consensusOrder, pos, newState, genesis, newHistory, newResults, timestamp);
547+
}
523548

524549
private void validateSignatures(State s, AVector<SignedData<Block>> blocks, long start, long end) {
525550
Consumer<SignedData<ATransaction>> transactionValidator=st->{
@@ -721,4 +746,10 @@ public boolean isReadyToPublish() {
721746
public Hash getGenesisHash() {
722747
return getGenesisState().getHash();
723748
}
749+
750+
public long getHistoryPosition() {
751+
return historyPosition;
752+
}
753+
754+
724755
}

convex-core/src/main/java/convex/core/util/Utils.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import convex.core.data.Blob;
3434
import convex.core.data.Hash;
3535
import convex.core.data.impl.ALongBlob;
36+
import convex.core.data.prim.AInteger;
3637
import convex.core.lang.RT;
3738

3839
/**
@@ -651,7 +652,18 @@ public static int byteLength(BigInteger bi) {
651652
public static int toInt(Object v) {
652653
if (v instanceof Integer) return (Integer) v;
653654
if (v instanceof String) {
654-
return Integer.parseInt((String) v);
655+
try {
656+
return Integer.parseInt((String) v);
657+
} catch (NumberFormatException e) {
658+
throw new IllegalArgumentException("String cannot be converted to an integer");
659+
}
660+
}
661+
if (v instanceof ACell) {
662+
AInteger cv=AInteger.parse(v);
663+
if (cv==null) throw new IllegalArgumentException("Cell not a integer numeric value: " + v);
664+
Integer result=(int)cv.longValue();
665+
if (result.longValue()==cv.doubleValue()) return result;
666+
throw new IllegalArgumentException("CVM numeric value not in Java Integer range");
655667
}
656668
if (v instanceof Number) {
657669
Number number = (Number) v;
@@ -662,6 +674,38 @@ public static int toInt(Object v) {
662674
}
663675
throw new IllegalArgumentException("Can't convert to int: " + v);
664676
}
677+
678+
/**
679+
* Converts an object to a Long value, handling Strings and arbitrary numbers.
680+
*
681+
* @param v An object representing a valid int value
682+
* @return The converted int value of the object
683+
* @throws IllegalArgumentException If the argument cannot be converted to an
684+
* int
685+
*/
686+
public static long toLong(Object v) {
687+
if (v instanceof Long) return (Integer) v;
688+
if (v instanceof String) {
689+
try {
690+
return Long.parseLong((String) v);
691+
} catch (NumberFormatException e) {
692+
throw new IllegalArgumentException("String cannot be converted to a Long");
693+
}
694+
}
695+
if (v instanceof ACell) {
696+
AInteger cv=AInteger.parse(v);
697+
if (cv==null) throw new IllegalArgumentException("Cell not a integer numeric value: " + v);
698+
return cv.longValue();
699+
}
700+
if (v instanceof Number) {
701+
Number number = (Number) v;
702+
long value = number.longValue();
703+
// following is safe, because double can represent any int
704+
if (value != number.doubleValue()) throw new IllegalArgumentException("Cannot coerce to long without loss:");
705+
return value;
706+
}
707+
throw new IllegalArgumentException("Can't convert to int: " + v);
708+
}
665709

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

13781422

13791423

1424+
13801425
}

convex-core/src/test/java/convex/core/cvm/PeerTest.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package convex.core.cvm;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
45
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
57

68
import java.io.IOException;
79
import java.nio.file.Files;
@@ -13,14 +15,18 @@
1315
import org.junit.jupiter.api.TestInstance.Lifecycle;
1416

1517
import convex.core.crypto.AKeyPair;
18+
import convex.core.cvm.transactions.Invoke;
1619
import convex.core.data.ACell;
1720
import convex.core.data.AMap;
1821
import convex.core.data.AccountKey;
1922
import convex.core.data.Keyword;
23+
import convex.core.data.ObjectsTest;
2024
import convex.core.exceptions.BadFormatException;
25+
import convex.core.exceptions.InvalidDataException;
2126
import convex.core.init.Init;
2227
import convex.core.util.FileUtils;
23-
28+
import convex.core.util.Utils;
29+
import convex.core.cpos.Block;
2430
import convex.core.cpos.CPoSConstants;
2531

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

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

69+
@Test
70+
public void testPeerUpdates() throws InvalidDataException {
71+
Peer p=Peer.create(KP, GENESIS);
72+
long ts=p.getTimestamp();
73+
74+
for (int i=0; i<10; i++) {
75+
Block b=Block.of(ts+i, KP.signData(Invoke.create(Init.GENESIS_ADDRESS, i+1, "(* "+i+" "+i+")")));
76+
p=p.proposeBlock(b);
77+
}
78+
assertEquals(ts+9,p.getTimestamp());
79+
assertEquals(0,p.getStatePosition());
80+
assertEquals(0,p.getFinalityPoint());
81+
assertEquals(10,p.getPeerOrder().getBlockCount());
82+
p=p.mergeBeliefs();
83+
p=p.mergeBeliefs();
84+
p=p.mergeBeliefs();
85+
p=p.mergeBeliefs();
86+
p=p.updateState();
87+
assertEquals(10,p.getStatePosition());
88+
assertEquals(81,Utils.toInt(p.getBlockResult(9).getResults().get(0).getValue()));
89+
90+
// check all invariants
91+
doPeerTest(p);
92+
93+
{ // Truncate to genesis
94+
Peer pt=p.truncateState(0);
95+
assertEquals(0,pt.getStatePosition());
96+
assertEquals(GENESIS,pt.getConsensusState());
97+
doPeerTest(pt);
98+
99+
// replay transactions up to consensus
100+
pt=pt.updateState();
101+
assertEquals(p.getConsensusState(),pt.getConsensusState());
102+
103+
assertEquals(pt.getBlockResult(7),p.getBlockResult(7));
104+
}
105+
106+
{ // Truncate to mid point
107+
Peer pt=p.truncateState(5);
108+
assertEquals(5,pt.getStatePosition());
109+
assertNotEquals(GENESIS,pt.getConsensusState());
110+
doPeerTest(pt);
111+
112+
// replay transactions up to consensus
113+
pt=pt.updateState();
114+
assertEquals(p.getConsensusState(),pt.getConsensusState());
115+
116+
assertEquals(pt.getBlockResult(7),p.getBlockResult(7));
117+
118+
}
119+
120+
{ // Truncate to distant future
121+
Peer pt=p.truncateState(Long.MAX_VALUE);
122+
assertEquals(p.getStatePosition(),pt.getStatePosition());
123+
doPeerTest(pt);
124+
}
125+
}
126+
127+
private void doPeerTest(Peer pt) {
128+
assertTrue(pt.getGenesisHash().equals(GENESIS.getHash()));
129+
130+
long sp=pt.getStatePosition();
131+
long hp=pt.getHistoryPosition();
132+
assertTrue(sp>=hp);
133+
}
134+
62135
@Test
63136
public void testPeerStatus() {
64137
PeerStatus ps=PeerStatus.create(Address.ZERO, CPoSConstants.MINIMUM_EFFECTIVE_STAKE);
65138

139+
ObjectsTest.doCellTests(ps);
66140
}
67141
}

convex-core/src/test/java/convex/util/UtilsTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,29 @@ public void testToInt() {
285285
assertEquals(7, Utils.toInt(7.0f));
286286
assertEquals(8, Utils.toInt("8"));
287287
assertEquals(-1, Utils.toInt("-1"));
288+
assertEquals(Integer.MAX_VALUE, Utils.toInt("2147483647"));
289+
assertEquals(Integer.MIN_VALUE, Utils.toInt(CVMLong.parse("-2147483648")));
290+
291+
assertThrows(IllegalArgumentException.class, ()->Utils.toInt("foo"));
292+
assertThrows(IllegalArgumentException.class, ()->Utils.toInt(1.5));
293+
assertThrows(IllegalArgumentException.class, ()->Utils.toInt(null));
294+
}
295+
296+
@Test
297+
public void testToLong() {
298+
assertEquals(1, Utils.toLong(1));
299+
assertEquals(7, Utils.toLong(7.0f));
300+
assertEquals(8, Utils.toLong("8"));
301+
assertEquals(-1, Utils.toLong("-1"));
302+
assertEquals(Integer.MAX_VALUE, Utils.toLong("2147483647"));
303+
assertEquals(Integer.MIN_VALUE, Utils.toLong(CVMLong.parse("-2147483648")));
304+
305+
assertEquals(Long.MAX_VALUE, Utils.toLong("9223372036854775807"));
306+
307+
assertThrows(IllegalArgumentException.class, ()->Utils.toLong("foo"));
308+
assertThrows(IllegalArgumentException.class, ()->Utils.toLong(1.5));
309+
assertThrows(IllegalArgumentException.class, ()->Utils.toLong(null));
310+
288311
}
289312

290313
@Test

convex-peer/src/main/java/convex/peer/CVMExecutor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import convex.core.cpos.Belief;
1111
import convex.core.cvm.Peer;
12+
import convex.core.exceptions.TODOException;
1213
import convex.core.util.LatestUpdateQueue;
1314
import convex.core.util.LoadMonitor;
1415

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

6263
public void syncPeer(Server base) {
6364
// TODO Auto-generated method stub
64-
65+
throw new TODOException();
66+
}
67+
68+
public synchronized void recalcState(long pos) {
69+
// TODO Auto-generated method stub
70+
peer=peer.recalcState(pos);
6571
}
6672

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

100106

101107

108+
109+
102110
}

convex-peer/src/main/java/convex/peer/Server.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,13 @@ public synchronized void launch() throws LaunchException, InterruptedException {
356356
executor.persistPeerData();
357357

358358
HashMap<Keyword, Object> config = getConfig();
359+
360+
if (config.containsKey(Keywords.RECALC)) try {
361+
Long pos=Utils.toLong(config.get(Keywords.RECALC));
362+
executor.recalcState(pos);
363+
} catch (Exception e) {
364+
throw new LaunchException("Launch failed to recalculate state: "+e,e);
365+
}
359366

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

380+
381+
373382
// Start threaded components
374383
manager.start();
375384
queryHandler.start();
@@ -381,9 +390,9 @@ public synchronized void launch() throws LaunchException, InterruptedException {
381390
goLive();
382391
log.info( "Peer server started on port "+nio.getPort()+" with peer key: {}",getPeerKey());
383392
} catch (ConfigException e) {
384-
throw new LaunchException("Launch failed due to config problem",e);
393+
throw new LaunchException("Launch failed due to config problem: "+e,e);
385394
} catch (IOException e) {
386-
throw new LaunchException("Launch failed due to IO Error",e);
395+
throw new LaunchException("Launch failed due to IO Error: "+e,e);
387396
} finally {
388397
Stores.setCurrent(savedStore);
389398
}

0 commit comments

Comments
 (0)