Check out my website federicozanardo.it
federicozanardo / stipula-node Goto Github PK
View Code? Open in Web Editor NEWImplementation of Stipula language (Master's degree, UniPD, 2023).
Implementation of Stipula language (Master's degree, UniPD, 2023).
Check out my website federicozanardo.it
Add asset type support to the following instructions:
Structure in modules the repository, i.e. virtual machine, compiler, storage, API, ...
In the following snippet of bytecode:
fn accept Borrower Using
args:
PUSH float :y
AINST asset y iop890
ASTORE y
start:
ALOAD y
GLOAD cost
ISEQ
JMPIF if_branch
JMP end
if_branch:
// start deposit
ALOAD y
GLOAD wallet
DEPOSIT wallet
// end deposit
end:
HALT
if the asset amount received y
is not equal to the value cost
, the execution goes in the else-branch. The execution returns a positive result and the contract goes on to the next state. This is bad behavior to avoid.
A proposal is to create a new type error
and to push an error in the stack. When the error is popped from the stack, the execution is halted and contract state does not change.
Remake from issue #1.
Message format for agreement
Checks to do:
parties
object can't have more than 3 public keys and less than 1 public key){
"message": {
"agreement": {
"contract_id": "asd123",
"arguments": [
{
"cost": "12",
"rent_time": "1"
}
],
"parties": [
{
"Lender": "pubKeyLender"
},
{
"Borrower": "pubKeyBorrower"
}
]
}
},
"signatures": [
{
"Lender": "sigHashMessage"
},
{
"Borrower": "sigHashMessage"
}
]
}
Message format for generic function call
Checks to do:
{
"message": {
"call": {
"contract_id": "asd123",
"contract_instance_id": "dsa321",
"function": "offer",
"arguments": [
{
"z": "1"
}
]
}
},
"signatures": [
{
"Lender": "sigHashMessage"
}
]
}
Example of agreement function:
fn agreement
// define global variables
global:
GINST addr Lender
GINST addr Borrower
GINST asset wallet
GINST int cost
GINST int rent_time
GINST int use_code
// define arguments of this function
args:
PUSH addr :Lender
GSTORE Lender
PUSH addr :Borrower
GSTORE Borrower
PUSH int :cost
GSTORE cost
PUSH int :rent_time
GSTORE rent_time
// define the body of the function
start:
HALT
At the end of the agreement call, if it succeeds, generate a contract_instance_id
and return it.
Example of offer function:
fn Proposal Lender offer
args:
PUSH int :z
AINST int z
ASTORE z
start:
ALOAD z
GSTORE use_code
HALT
Example of end function:
fn Using Borrower end
start:
// body of the function
HALT
Develop a compiler that given a Stipula contract, compile it in Stipula bytecode.
The instructions ISGT
and ISGE
can be substituted as:
ISGT = NOT(ISLE)
ISGE = NOT(ISLT)
This thread has to handle all the contract events. When an event has been triggered, the thread sends a request to the queue manager. Furthermore, the thread has to handle the scheduling of the events.
Implement the deployment of a smart contract. There will be a smart contract file, and this indicates the deployment in the Stipula layer. When the parties want to run a contract, they sign a message in order to start an instantiation of a specific smart contract. So, it will be generated a new file that tracks the current state of the contract.
Starting from the fact that the final purpose is to include different modules (i.e. compiler, API, storage, ...), it is reasonable to change the name of the repository.
The execution of a smart contract goes on through function calls. To prevent an attacker can carry on the state of the smart contract without the consensus of the parties, a party must sign the hash of the function call and send it to the SVM. In this way, only the authorized party of the contract can try to carry on the state of the contract.
Example of a smart contract function:
fn ae23bc6697ff091cdcda5f9e15ca73b808e0aa14a504701930694d2f7784b17f
APUSH int :arg_0
AINST int a
ASTORE a
APUSH int :arg_1
AINST int b
ASTORE b
ALOAD a
ALOAD b
AHALT
start:
// body of the function
HALT
:arg_0
is a placeholder. Placeholders can be used only for arguments.
Example of a function call:
fn ae23bc6697ff091cdcda5f9e15ca73b808e0aa14a504701930694d2f7784b17f
APUSH int 5
AINST int a
ASTORE a
APUSH int 4
AINST int b
ASTORE b
ALOAD a
ALOAD b
AHALT
Calculate the hash (SHA256) of this code: 3ab50b3d44c849c3aca3ab2a8f6ed2f6b3e69cf25f1701c7c23cef26ccf08691
(service used: https://emn178.github.io/online-tools/sha256.html)
Generate RSA keys 1024 bit (service used: https://www.javainuse.com/rsagenerator):
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXsHhSIR/zPkEx9OhQRNJ/urFaG2dBJhmYIPwGoVmhLdAwvCmGtxkrf4ToXDhzAkbabj+zaTyvl2xFcrN+SQTZCFMPyWOeTifrCkLgJ2q2BH4crleCnGh6QEaqfTxv1SBNAcVpVuTQs/K+g/9FdF9d05u/rQHC8ESrnNFv5WD6MwIDAQAB
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJeweFIhH/M+QTH06FBE0n+6sVobZ0EmGZgg/AahWaEt0DC8KYa3GSt/hOhcOHMCRtpuP7NpPK+XbEVys35JBNkIUw/JY55OJ+sKQuAnarYEfhyuV4KcaHpARqp9PG/VIE0BxWlW5NCz8r6D/0V0X13Tm7+tAcLwRKuc0W/lYPozAgMBAAECgYEAim55D/K89r6svsuTq5VzSS/2pJX3oEQF/Yi0l9RuOKLXqXI+r3uvk7vXbjqjJYpBYRxWNARpZpHZNHPeDPTUX1eVCii3TLfOmtFsEN4KpYk+l7V20ID+QqkekcDyz0qxKsuWvWr3B74DWn2DqPKSQOlb41/TiQoc70ooLUIwP1ECQQDXen6xytkFRrBpXbYSdAbym9qmzyCbDdsUx1SALOxxSIMBNATxQFDyvmf/NGkQwITJv+39zIOWrnEwRNVeuvubAkEAtDcOKdH99I6BYsnjnW8q1tl3Zm/7JGKJmZKcT76v/Q7s+1f8IEhln5LjMOthbZ5PikFlVZVWGNAy4tMV3YThSQJAAoqiVeyDlMlZqVR+okcWEeR+trr4snt+Wwdi2sQs4cUuLmRzrnjIu6Q9S8hNePIcXtjRsM2pu6xBD4WwUpa4AwJAZl4bqn/BHOjR8Da0F5qtH+vZmhOj+fALL3QLXHT57OpMjR1Wd1QIfdNnQEOETUsu7V7mW+3/QQsKzLOQ6QrxGQJBAJI5cCr+STZTd02FtImMS6IKKiEcTXuwdto9ZzyCRKsbRtJyg6pHjLpxAwZX8aDpfK0N+rOxUNxrSmHc0uMjrxE=
Result of signing the hash (service used: https://www.javainuse.com/rsagenerator): XYQGxbebfEttDCUkSkNhBqWVUodJPd79vWnTLNOAcn7e548iSpB7g/fYnX4k5qJPHO1W8HFYVnwQeYl6hjKhYmrDHtrPCIhae3cdNQhnlw+/ml343DlCtvZFjKlffJFjFwxVBrbhQpRqthuEy76w+v4khdZDfUlSUdlIK6zcX40=
By using the public key, the result is the original hash (service used: https://www.javainuse.com/rsagenerator): 3ab50b3d44c849c3aca3ab2a8f6ed2f6b3e69cf25f1701c7c23cef26ccf08691
This is the cryptographic proof needed in order to avoid that an attacker can carry on the state of the contract.
Reference #5 and #12.
It is necessary to manage carefully the storing process in the commitment layer. The SVM pushes, in a particular stack (only the push
operation is allowed), all the information that must be stored in the commitment layer. The "execution"* phase (not the virtual machine), when the SVM completed the execution and returned a positive result, must wait to store the information before carrying on the state of the contract.
* For "execution" phase I mean the phase that precedes and follows the execution of the bytecode by the SVM. Tasks performed in this phase are loading the program for the SVM and checking if there is information to store in the commitment layer.
| Other stuff |
----------------------------------------------------------------------------------------------
| "Execution" phase (i.e. loadProgram, checkLastInstructionIsHalt, ...) |
----------------------------------------------------------------------------------------------
| SVM execution |
----------------------------------------------------------------------------------------------
| "Execution" phase (i.e. store information in the commitment layer, ...) |
----------------------------------------------------------------------------------------------
| (Maybe other stuff?) |
Reference to #1 and #2.
As shown in #1, it is necessary to mark the beginning of the function body. In order to do that, it is necessary to mark the body with the label start:
. So, introduce checks on the program loading phase and raise an error if this label is missing.
Other labels to add are:
global:
: define the piece of code in order to manage the global variables. This label can be used only in the agreement
functionargs:
: define the piece of code in order to manage the argumentsImplement a first version of a SDK, in order to build applications based on the Stipula node.
It is necessary to develop additional instructions in order to:
At the moment, the current format of the instructions is not effective with the current development.
The ScriptVM is not performing correctly the signature verification of the script.
This thread has to communicate with the client. It has to enqueue the user's request in the queue manager and wait until a response is ready. At this point, the thread has to pack the response in a message and send it.
Reference #7.
In order to manage safely the asset transfers, maybe it is necessary to compute the transfer in a separated stack, in order to treat the transfer "atomically". In this way, there is the default stack and an asset-transfer-dedicated stack in which only particular instructions are allowed. This set of instructions can be used only in this specific stack, they can't be used in the default stack or for arguments.
Develop a data structure in order to manage properly the state of the virtual machine.
Reference #1 and #6.
It is necessary to find a way to store the global variables and their evolution by time.
It is necessary to implement interfaces/protocols to communicate with the commitment layer.
This queue manager has to handle all the requests received by the client handler threads and the event trigger thread. These requests will be dequeued by the SVM thread in order to execute the code.
Implement the following instructions:
AND
OR
NOT
The line to insert in the ClientHandler
:
Thread thread = new Thread(new WaiterThread(this, responsesToSend));
ConsumerThread
:
public class ConsumerThread extends Thread {
private final RequestQueue queue;
public ConsumerThread(String name, RequestQueue queue) {
super(name);
this.queue = queue;
}
@Override
public void run() {
int i = 0;
Pair<Thread, Message> pair;
Message message;
while (true) {
System.out.println("ConsumerThread: Popping value number " + (i + 1) + "...");
try {
pair = this.queue.dequeue();
message = pair.getSecond();
if (message != null) {
i++;
if (message instanceof AgreementCall) {
AgreementCall agreementCall = (AgreementCall) message;
System.out.println("\t" + queue);
System.out.println("\treceived: " + agreementCall);
}
}
} catch (QueueUnderflowException e) {
// throw new RuntimeException(e);
try {
System.out.println("ConsumerThread: I'm waiting...");
synchronized (this) {
this.wait();
}
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
}
}
ProducerThread
:
public class ProducerThread extends Thread {
private final RequestQueue queue;
private final Thread consumer;
public ProducerThread(String name, RequestQueue queue, Thread consumer) {
super(name);
this.queue = queue;
this.consumer = consumer;
}
@Override
public void run() {
// Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName() + ": Start");
AgreementCall firstMessage = new AgreementCall(
Thread.currentThread().getName() + "a",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
AgreementCall secondMessage = new AgreementCall(
Thread.currentThread().getName() + "b",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
AgreementCall thirdMessage = new AgreementCall(
Thread.currentThread().getName() + "c",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push first value");
try {
this.queue.enqueue(null, firstMessage);
synchronized (this.consumer) {
this.consumer.notify();
}
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push second value");
try {
this.queue.enqueue(null, secondMessage);
synchronized (this.consumer) {
this.consumer.notify();
}
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push third value");
try {
this.queue.enqueue(new EventTriggerSchedulingRequest(
new EventTriggerRequest(
Thread.currentThread().getName() + "c",
"d",
"e"
),
5
)
);
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": End");
}
}
WaiterThread
:
public class WaiterThread implements Runnable {
private final Thread clientHandler;
private final HashMap<String, Response> commonSpace;
public WaiterThread(Thread clientHandler, HashMap<String, Response> commonSpace) {
this.clientHandler = clientHandler;
this.commonSpace = commonSpace;
}
@Override
public void run() {
System.out.println("WaiterThread: Hey! I'm going to sleep for 5 seconds...");
try {
Thread.sleep(5000);
} catch (InterruptedException error) {
throw new RuntimeException(error);
}
System.out.println("WaiterThread: I'm here!");
System.out.println("WaiterThread: Put data in the common space for " + this.clientHandler.getName());
if (this.commonSpace.containsKey(this.clientHandler.getName())) {
this.commonSpace.put(this.clientHandler.getName(), new SuccessDataResponse("ack from WaiterThread"));
System.out.println("WaiterThread: Now I'll notify the thread " + this.clientHandler.getName());
synchronized (this.clientHandler) {
this.clientHandler.notify();
}
System.out.println("WaiterThread: Bye bye!");
} else {
System.out.println("WaiterThread: Oh no! There is no reference in the common space for this thread " + this.clientHandler.getName());
}
}
}
LazyProducerThread
:
public class LazyProducerThread implements Runnable {
private final RequestQueue queue;
public LazyProducerThread(RequestQueue queue) {
this.queue = queue;
}
@Override
public void run() {
// Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName() + ": Start");
AgreementCall firstMessage = new AgreementCall(
Thread.currentThread().getName() + "a",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
AgreementCall secondMessage = new AgreementCall(
Thread.currentThread().getName() + "b",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
AgreementCall thirdMessage = new AgreementCall(
Thread.currentThread().getName() + "c",
new HashMap<String, String>(),
new HashMap<String, Address>()
);
try {
Thread.sleep(7500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push first value");
/*try {
this.queue.enqueue(firstMessage);
} catch (QueueOverflowException | InterruptedException e) {
throw new RuntimeException(e);
}*/
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push second value");
/*try {
this.queue.enqueue(secondMessage);
} catch (QueueOverflowException | InterruptedException e) {
throw new RuntimeException(e);
}*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ": Push third value");
/*try {
this.queue.enqueue(thirdMessage);
} catch (QueueOverflowException | InterruptedException e) {
throw new RuntimeException(e);
}*/
System.out.println(Thread.currentThread().getName() + ": End");
}
}
Ref. #23
Ref. #23
Define a lightweight virtual machine for a script evaluation for single-use seals. This lightweight version is able to mange only string types. The only instructions allowed are:
PUSH
DUP
SHA256
EQUAL
CHECKSIG
While executing the bytecode, save all the triggers to set up. After the execution, develop the code to send the EventTrigger to the EventTriggerHandler. Find a way in order to allow the communication between VirtualMachine thread and EventTriggerHandler (maybe a queue?).
RequestQueue requestQueue = new RequestQueue();
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": I'm waiting to push in the queue...");
try {
requestQueue.enqueue(this.getName());
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": Pushed in the queue");
}
};
Thread thread2 = new Thread("thread2") {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": I'm waiting to push in the queue...");
try {
requestQueue.enqueue(this.getName());
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": Pushed in the queue");
}
};
Thread thread3 = new Thread("thread3") {
@Override
public void run() {
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": I'm waiting to push in the queue...");
try {
requestQueue.enqueue(this.getName());
} catch (QueueOverflowException e) {
throw new RuntimeException(e);
}
System.out.println(this.getName() + ": Pushed in the queue");
}
};
thread1.start();
thread2.start();
thread3.start();
Manage the storage properly an test the server.
Implement a type to manage time. This is useful in order to trigger the obligations. The object manages only seconds.
It is necessary to implement in a sort of way the concept of wallets. Maybe, the concept of a wallet is just the software that handles RSA keys and groups all the UTXOs of the different assets. In this way, on the Stipula layer, it is necessary to store only the addresses.
It is necessary to implement a structure to manage assets. There must be no way to move the resources in Stipula, except through a contract. Maybe, it could reasonable to implement an asset by UTXO. The reasons are:
In order to avoid having a huge number of UTXOs with a small amount of assets, it is necessary to develop a way to "compact" the UTXOs. The ideal case is to have just one input for the payment and at most two outputs: an output is for the payee (mandatory) and the other is the remainder (optional).
4 threads:
The socket thread when receiving a message forwards the request to a queue in the SVM thread. In this way, the SVM pops a request from that queue and queries the scheduled events thread in order to determine if there is a scheduled event to execute before satisfying the request. The queue will represent a bottleneck in the implementation, in the future a better design is mandatory.
Reference #7 and #9.
It is necessary to develop new instructions in order to handle asset transfers. Maybe there will be a set of instructions dedicated for the asset-transfer-dedicated stack and a set of instructions related to asset transfer that allow to instantiate the asset-transfer-dedicated stack.
In order to set up a trigger, there is the need of creating a new instruction TRIGGER <obligation function name>
that takes in input a Time
value in order to set up when to trigger the obligation.
Reference to #1.
In order to manage properly the arguments of a function call, it is necessary to create a specific set of instructions that manage the space relating to the arguments and the global variables. At the moment, the instructions that must be implemented are: AINST
, ASTORE
, ALOAD
, GINST
, GSTORE
, GLOAD
.
Ref. #23
It is necessary to implement a structure to manage assets. There must be no way to move the resources in Stipula, except through a contract. Maybe, it could reasonable to implement an asset by UTXO. The reasons are:
In order to avoid having a huge number of UTXOs with a small amount of assets, it is necessary to develop a way to "compact" the UTXOs. The ideal case is to have just one input for the payment and at most two outputs: an output is for the payee (mandatory) and the other is the remainder (optional).
The Stipula node (in the future, the Stipula network) does not allow sending and receiving assets directly: all the transfers must happen via contract (i.e., implement a contract for swapping assets). So, a contract can receive a UTXO and produce UTXOs in output.
In order to manage safely the asset transfers, maybe it is necessary to compute the transfer in a separate stack, in order to treat the transfer "atomically". In this way, there is the default stack and an asset-transfer-dedicated stack in which only particular instructions are allowed. This set of instructions can be used only in this specific stack, they can't be used in the default stack or for arguments.
It is necessary to develop new instructions in order to handle asset transfers. Maybe there will be a set of instructions dedicated to the asset-transfer-dedicated stack and a set of instructions related to asset transfer that allows instantiating the asset-transfer-dedicated stack.
We can call the UTXOs Single Use Seals. A user can send a single-use seal to a contract. A single-use seal is locked by a script: the user can unlock the script (so, spend the asset) by providing proof of property of that asset. In order to do that, the user must provide a signature about his/her public key.
Example:
PUSH str <signature> PUSH str <pub_key> DUP SHA256 PUSH str <pub_key_hash> EQUAL CHECKSIG
Explanation:
DUP SHA256 PUSH str <pub_key_hash> EQUAL CHECKSIG
: this is the locking script in the single-use sealPUSH str <signature> PUSH str <pub_key>
: this is the unlocking script provided by the user via inputIn this way, the user can prove the property of the asset that he/she wants to spend. In the current implementation, <pub_key_hash>
corresponds to a user's address. The signature
maybe should be the signature of the single-use seal id. If the user signs the public key, an attacker can copy that signature in all the single-use seals in order to steal the funds.
Instructions needed:
DUP
: duplicate the top element of the stackSHA256
: it pops the top element from the stack, compute the hash (256-bit) of that element and push the resultEQUAL
: it pops the first two elements from the stack, checks if they are equal and if the result is true, this instruction does not push anything in the stack. Otherwise, if the two elements are not equal, it raises an errorCHECKSIG
: it pops the first two elements from the stack. The first one must be a public key and the second one must be a signature. This instruction verifies if the signature is valid or not. It pushes true if the signature is valid, otherwise false.A user sends a single-use seal to the contract, and the contract verifies the property of the asset (it tries to unlock the script of the single-use seal). If the check is successful, then the contract breaks the single-use seal and keeps the asset inside itself. When a contract wants to pay a party, it generates a single-use seal with the public key of the party (so, it generates a locking script in the single-use seal).
When a party wants to pay, he/she provides a UTXO or more than one UTXO. If there is more than one UTXO, the system "merges" the tokens in one UTXO: create a transfer to the user himself/herself with inputs of the UTXOs and in output, there will be just one UTXO. The amount of tokens in the output UTXO is the sum of the amount of tokens in the input UTXOs. In other words, the "merge" is a transfer that accepts more than one UTXO in input and produces an output only one UTXO in output. There will not be any remainder of UTXO. The sender and the receiver of this transfer are the address (that is the user).
It will accept all the client connections and delegate the communication in a dedicated thread.
Create error codes for socket responses
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.