diff --git a/convex-core/src/main/java/convex/core/cpos/BeliefMerge.java b/convex-core/src/main/java/convex/core/cpos/BeliefMerge.java index dea24043a..189ac79df 100644 --- a/convex-core/src/main/java/convex/core/cpos/BeliefMerge.java +++ b/convex-core/src/main/java/convex/core/cpos/BeliefMerge.java @@ -207,7 +207,7 @@ Index> vote( final Index> resultOrders = filteredOrders; if (!consensusOrder.consensusEquals(myOrder)) { diff --git a/convex-core/src/main/java/convex/core/cvm/Peer.java b/convex-core/src/main/java/convex/core/cvm/Peer.java index 770b6fac3..d6c57635c 100644 --- a/convex-core/src/main/java/convex/core/cvm/Peer.java +++ b/convex-core/src/main/java/convex/core/cvm/Peer.java @@ -25,10 +25,12 @@ import convex.core.data.Maps; import convex.core.data.Ref; import convex.core.data.SignedData; +import convex.core.data.Strings; import convex.core.data.Vectors; import convex.core.data.prim.CVMLong; import convex.core.exceptions.InvalidDataException; import convex.core.init.Init; +import convex.core.lang.RT; import convex.core.store.AStore; import convex.core.store.Stores; import convex.core.util.Utils; @@ -608,6 +610,45 @@ public Peer proposeBlock(Block block) { // result=result.updateState(); return result; } + + public Result checkTransaction(SignedData sd) { + + // TODO: throttle? + ATransaction tx=RT.ensureTransaction(sd.getValue()); + + // System.out.println("transact: "+v); + if (tx==null) { + return Result.error(ErrorCodes.FORMAT,Strings.BAD_FORMAT); + } + + State s=getConsensusState(); + AccountStatus as=s.getAccount(tx.getOrigin()); + if (as==null) { + return Result.error(ErrorCodes.NOBODY, Strings.NO_SUCH_ACCOUNT); + } + + if (tx.getSequence()<=as.getSequence()) { + return Result.error(ErrorCodes.SEQUENCE, Strings.OLD_SEQUENCE); + } + + AccountKey expectedKey=as.getAccountKey(); + if (expectedKey==null) { + return Result.error(ErrorCodes.STATE, Strings.NO_TX_FOR_ACTOR); + } + + AccountKey pubKey=sd.getAccountKey(); + if (!expectedKey.equals(pubKey)) { + return Result.error(ErrorCodes.SIGNATURE, Strings.WRONG_KEY ); + } + + if (!sd.checkSignature()) { + // SECURITY: Client tried to send a badly signed transaction! + return Result.error(ErrorCodes.SIGNATURE, Strings.BAD_SIGNATURE); + } + + // All checks passed OK! + return null; + } /** * Gets the Final Point for this Peer diff --git a/convex-core/src/main/java/convex/core/init/Init.java b/convex-core/src/main/java/convex/core/init/Init.java index 79186534b..460f5baba 100644 --- a/convex-core/src/main/java/convex/core/init/Init.java +++ b/convex-core/src/main/java/convex/core/init/Init.java @@ -1,7 +1,6 @@ package convex.core.init; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import convex.core.Coin; @@ -391,7 +390,7 @@ private static State addStandardLibraries(State s) { * @param peerKeys * @return */ - public static State createTestState(ArrayList peerKeys) { + public static State createTestState(List peerKeys) { State s=createState(peerKeys); s = doActorDeploy(s, "/convex/asset/nft/tokens.cvx"); s = doActorDeploy(s, "/convex/lab/play.cvx"); diff --git a/convex-peer/src/main/java/convex/api/Convex.java b/convex-peer/src/main/java/convex/api/Convex.java index cde30683b..94474e3ac 100644 --- a/convex-peer/src/main/java/convex/api/Convex.java +++ b/convex-peer/src/main/java/convex/api/Convex.java @@ -47,7 +47,6 @@ import convex.core.store.Stores; import convex.core.util.Utils; import convex.net.IPUtils; -import convex.net.ResultConsumer; import convex.peer.Config; import convex.peer.Server; diff --git a/convex-peer/src/main/java/convex/api/ConvexDirect.java b/convex-peer/src/main/java/convex/api/ConvexDirect.java new file mode 100644 index 000000000..085ad424c --- /dev/null +++ b/convex-peer/src/main/java/convex/api/ConvexDirect.java @@ -0,0 +1,140 @@ +package convex.api; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +import convex.core.Result; +import convex.core.ResultContext; +import convex.core.cpos.Block; +import convex.core.crypto.AKeyPair; +import convex.core.cvm.Address; +import convex.core.cvm.Peer; +import convex.core.cvm.PeerStatus; +import convex.core.cvm.State; +import convex.core.cvm.transactions.ATransaction; +import convex.core.data.ACell; +import convex.core.data.AccountKey; +import convex.core.data.Blob; +import convex.core.data.Hash; +import convex.core.data.SignedData; +import convex.core.data.prim.CVMLong; +import convex.core.message.Message; +import convex.core.store.AStore; +import convex.core.util.Utils; + +/** + * Convex API instance that directly interacts with a Peer instance + */ +public class ConvexDirect extends Convex { + + protected Peer peer; + private boolean isConnected=true; + + protected ConvexDirect(Address address, AKeyPair keyPair, Peer initial) { + super(address, keyPair); + this.peer=initial; + } + + public static ConvexDirect create(AKeyPair peerKey,State state) { + AccountKey key=peerKey.getAccountKey(); + PeerStatus ps= state.getPeer(key); + if (ps==null) throw new IllegalStateException("Peer does not exist in desired state"); + Address cont=ps.getController(); + return new ConvexDirect(cont,peerKey,Peer.create(peerKey, state)); + } + + @Override + public boolean isConnected() { + return isConnected; + } + + @Override + public synchronized CompletableFuture transact(SignedData signedTransaction) { + try { + CVMLong id=CVMLong.create(getNextID()); + Peer p=peer; + Result failure=p.checkTransaction(signedTransaction); + if (failure!=null) { + return CompletableFuture.completedFuture(failure.withID(id)); + } + + long ts=Utils.getCurrentTimestamp(); + Block block=Block.of(ts, signedTransaction); + + // Peer updates + p=p.updateTimestamp(ts); + p=p.proposeBlock(block); + p=p.mergeBeliefs(); + p=p.updateState(); + long blockNum=p.getPeerOrder().count()-1; + peer=p; + + Result result= p.getResult(blockNum, 0); + return CompletableFuture.completedFuture(result.withID(id)); + } catch (Exception e) { + return CompletableFuture.completedFuture(Result.fromException(e)); + } + } + + @Override + public CompletableFuture messageRaw(Blob message) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture message(Message message) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture acquire(Hash hash, AStore store) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture requestStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture requestChallenge(SignedData data) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture query(ACell query, Address address) { + CVMLong id=CVMLong.create(getNextID()); + ResultContext rc= peer.executeQuery(query, address); + return CompletableFuture.completedFuture(Result.fromContext(id,rc)); + } + + @Override + public void close() { + peer=null; + } + + @Override + public String toString() { + return "Direct client with peer state: "+peer.getConsensusState().getHash(); + } + + @Override + public InetSocketAddress getHostAddress() { + return null; + } + + @Override + public void reconnect() throws IOException, TimeoutException, InterruptedException { + isConnected=true; + } + + + +} diff --git a/convex-peer/src/main/java/convex/peer/TransactionHandler.java b/convex-peer/src/main/java/convex/peer/TransactionHandler.java index 6ed026e7f..f8405c624 100644 --- a/convex-peer/src/main/java/convex/peer/TransactionHandler.java +++ b/convex-peer/src/main/java/convex/peer/TransactionHandler.java @@ -38,7 +38,6 @@ import convex.core.data.prim.CVMLong; import convex.core.exceptions.BadFormatException; import convex.core.exceptions.MissingDataException; -import convex.core.lang.RT; import convex.core.lang.Reader; import convex.core.message.Message; import convex.core.util.LoadMonitor; @@ -156,7 +155,7 @@ protected void processMessage(Message m) throws InterruptedException { SignedData sd = (SignedData) v.get(2); // Check our transaction is valid and we want to process it - Result error=checkTransaction(sd); + Result error=server.getPeer().checkTransaction(sd); if (error!=null) { m.returnResult(error.withSource(SourceCodes.PEER)); return; @@ -179,44 +178,7 @@ protected void processMessage(Message m) throws InterruptedException { } } - private Result checkTransaction(SignedData sd) { - // TODO: throttle? - ATransaction tx=RT.ensureTransaction(sd.getValue()); - - // System.out.println("transact: "+v); - if (tx==null) { - return Result.error(ErrorCodes.FORMAT,Strings.BAD_FORMAT); - } - - State s=server.getPeer().getConsensusState(); - AccountStatus as=s.getAccount(tx.getOrigin()); - if (as==null) { - return Result.error(ErrorCodes.NOBODY, Strings.NO_SUCH_ACCOUNT); - } - - if (tx.getSequence()<=as.getSequence()) { - return Result.error(ErrorCodes.SEQUENCE, Strings.OLD_SEQUENCE); - } - - AccountKey expectedKey=as.getAccountKey(); - if (expectedKey==null) { - return Result.error(ErrorCodes.STATE, Strings.NO_TX_FOR_ACTOR); - } - - AccountKey pubKey=sd.getAccountKey(); - if (!expectedKey.equals(pubKey)) { - return Result.error(ErrorCodes.SIGNATURE, Strings.WRONG_KEY ); - } - - if (!sd.checkSignature()) { - // SECURITY: Client tried to send a badly signed transaction! - return Result.error(ErrorCodes.SIGNATURE, Strings.BAD_SIGNATURE); - } - - // All checks passed OK! - return null; - } /** * Sets a request observer, which will be called whenever the Peer diff --git a/convex-peer/src/test/java/convex/api/ConvexDirectTest.java b/convex-peer/src/test/java/convex/api/ConvexDirectTest.java new file mode 100644 index 000000000..0a19fed77 --- /dev/null +++ b/convex-peer/src/test/java/convex/api/ConvexDirectTest.java @@ -0,0 +1,40 @@ +package convex.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import convex.core.Result; +import convex.core.crypto.AKeyPair; +import convex.core.cvm.Address; +import convex.core.cvm.State; +import convex.core.data.prim.CVMLong; +import convex.core.init.Init; +import convex.core.init.InitTest; +import convex.core.lang.ACVMTest; + +/** + * Tests for Convex Direct client + */ +public class ConvexDirectTest extends ACVMTest { + static final AKeyPair peerKey=AKeyPair.createSeeded(5675675); + + @Test public void testSetup() throws InterruptedException { + State state=Init.createTestState(List.of(peerKey.getAccountKey())); + ConvexDirect convex=ConvexDirect.create(peerKey,state); + Address addr=convex.getAddress(); + + assertTrue(convex.isConnected()); + assertEquals(InitTest.FIRST_PEER_ADDRESS,addr); + + assertEquals(addr,convex.query("*address*").join().getValue()); + + Result r=convex.transactSync("(+ 1 2)"); + assertFalse(r.isError(),()->"Expected ed error: "+r); + assertEquals(CVMLong.create(3),r.getValue()); + } +}