Giter Site home page Giter Site logo

stipula-node's Introduction

stipula-node's People

Contributors

federicozanardo avatar

Watchers

 avatar

stipula-node's Issues

Raise error if the funds to deposit are not equal to the amount requested

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.

Call a smart contract function

Remake from issue #1.

Message format for agreement

Checks to do:

  • Check the format of the message (i.e. parties object can't have more than 3 public keys and less than 1 public key)
  • Check the format of the message, according to the contract (i.e. if the contract must accept 3 parties and the message sent has only 2 parties, this message is not compliant with the contract)
  • Check the signatures
{
  "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:

  • Check the format of the message
  • Check the format of the message, according to the contract
  • Check that the signature is correct with the public key of the party, saved in the global variable
{
  "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

Develop a compiler that given a Stipula contract, compile it in Stipula bytecode.

Develop Event trigger handler

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.

Deployment of a smart contract

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.

Rename repository in stipula-node

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.

Call a smart contract function

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):

    • Public key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXsHhSIR/zPkEx9OhQRNJ/urFaG2dBJhmYIPwGoVmhLdAwvCmGtxkrf4ToXDhzAkbabj+zaTyvl2xFcrN+SQTZCFMPyWOeTifrCkLgJ2q2BH4crleCnGh6QEaqfTxv1SBNAcVpVuTQs/K+g/9FdF9d05u/rQHC8ESrnNFv5WD6MwIDAQAB
    • Private key: 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.

Manage the storage information process

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?)                                                                              |

Add a new labels in the bytecode

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 function
  • args:: define the piece of code in order to manage the arguments

Remove math instructions

At the moment, the current format of the instructions is not effective with the current development.

Develop the main structure of Client handler

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.

Manage asset transfers

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 queue for handling the requests

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.

ConsumerThread, ProducerThread, LazyProducerThread and WaiterThread

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");
    }
}

Set up the triggers in the VirtualMachine thread

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?).

Mutex test on RequestQueue

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();

Create Time type

Implement a type to manage time. This is useful in order to trigger the obligations. The object manages only seconds.

Implement a first draft of a wallet

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.

Implement assets

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:

  • It is easy to prove the property of certain assets (UTXO are cryptographically locked)
  • It is possible to verify the property of the current amount of an asset by following the "chain" of property of that amount
  • If a user wants to spend an amount of an asset, the user must give cryptographic proof that he/she owns that amount of asset

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).

Design a first draft of the node architecture

4 threads:

  • Main thread
  • A thread for scheduled events
  • A thread that keeps opening a socket in order to receive messages
  • A thread with the Stipula Virtual Machine

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.

Add instructions to manage asset transfers

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.

Add instructions to manage function arguments and global variables

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.

Implement assets

Reference #4, #7, #9 and #10.

General outline

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:

  • It is easy to prove the property of certain assets (UTXO are cryptographically locked)
  • It is possible to verify the property of the current amount of an asset by following the "chain" of property of that amount
  • If a user wants to spend an amount of an asset, the user must give cryptographic proof that he/she owns that amount of asset

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.

Implementation proposal

Introduction

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.

Proposal

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 seal
  • PUSH str <signature> PUSH str <pub_key>: this is the unlocking script provided by the user via input

In 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 stack
  • SHA256 : it pops the top element from the stack, compute the hash (256-bit) of that element and push the result
  • EQUAL: 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 error
  • CHECKSIG: 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.

How to use Single Use Seals?

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).

Problem: how to handle many UTXOs/Single Use Seals

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).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.