Skip to content

DivertiseAsia/NCN

Repository files navigation

Block-chain Framework

Table of content

  1. Overview
  2. Installation
  3. Basic framework utilization
    1. Node
    2. Config
    3. Serializer
  4. Main classes to implement
    1. Transaction
    2. Reward
    3. Database
  5. Advanced utilization
    1. Proof
    2. Hash
    3. Cryptography
  6. Helpers
    1. TransactionManager
    2. Framework
  7. TODO-List
    1. Proof
    2. Header files

Overview

This project is a C++ framework built to allow easy and fast development of any kind of block-chain.
The idea is to limit the development to transactions and data representation only. No need to implement anything else.
Of course, as a framework, it provides a big flexibility, therefore, it is possible to write advanced block-chain by configuring as many things as you want.

Note that everything can change to allow more flexibility.

Installation

To install the framework, you just have to copy/paste the folder block_chain in your project's root folder

Basic framework utilization

To create your own block-chain program, you just have to run the main objects provided by the framework.

Node

The node is the peer itself. You only have to create it and it will run.

Node node(config);

Config

The node is the peer itself. You only have to create it and it will run.

Config config("configuration file path", serializer, reward, Proof::WORK, Hash::HASH_MD5, Cryptography::CRYPTOGRAPHY_RSA);

The file needs to be a json file.

Example:
{
  "port": 3423,
  "encoding": "json",
  "debug": true
}

The parameters serializer, proof, hash, cryptography and reward are explained later.

Serializer

The serialization class is used to transform objects into string using Elements and strings into Objects using Elements. To use this class, you need to register your own transactions with a lambda expression

Serializer::unserializeTransaction
void add_transaction(int id, std::function<Transaction*()> transaction);
Example:
serial->add_transaction(money->get_type(), []() -> Transaction*{return new MoneyTransaction;});

This method can be replaced by another method:

serial->add_transaction<MoneyTransaction>();

It can also be replaced by the Framework helper.

Main classes to implement

In addition to these main classes, you need to implement some interfaces.

Transaction

Transactions are the most important thing to implement.

#include "./block_chain/chain/block/transaction/Transaction.h"

There is already a pure abstract class to implement, so you basicaly just have to fill the methods for your own Transaction class.

Useful methods

Transaction::get_type
int get_type() const override;

This method is used to give a type to a transaction. Each transaction type must have a different type. It is used as an ID.

Example:
int MoneyTransaction::get_type() const {
    return 2;
}
Transaction::operator==
bool operator==(Transaction* t) const override

This method is used to check if two transactions are the same

Example:
bool operator==(Transaction* t) const override {
    auto * s = dynamic_cast<MoneyTransaction*>(t);
    return amount == s->amount && target == s->target;
}

Serialization methods

Constructor
explicit Transaction();

For serialization, you need an empty default constructor.

Transaction::toElement
Element* toElement() const override;

This method is used to transform the object into an Element so it can easily be serialized

Example:
Element* MoneyTransaction::toElement() const {
    ElementObject* e = ElementCreator::object();
    return e->put("type", ElementCreator::create(get_type()))
            ->put("amount", ElementCreator::create(amount))
            ->put("target", ElementCreator::create(target));
}

The Element system is the key for serialization. You just have to fill all of the fields and their values. If you are using many differents type of Transactions, you should use a type to help your serializer.

Transaction::fromElement
void fromElement(ElementObject*, const Serializer*, const char* encoding) override;

This method is the opposite than toElement. It is used to build the object with a given Element object.

Example:
void MoneyTransaction::fromElement(ElementObject* e, const Serializer*, const char*) {
    e->getItem("amount", &amount);
    e->getItem("target", &target);
}

UI methods

Transaction::description
std::string description() const override;

This method is used to show the description of the transaction in the UI for the user to choose.

Example:
std::string MoneyTransaction::description() const {
    return "give money";
}
Transaction::fill_data
void fill_data() override;

This method is used to ask the user to fill the transaction's data

Example:
void MoneyTransaction::fill_data() {
    do {
        std::cout << "Amount of money: " << std::endl;
        std::cin >> amount;
    }while(amount < 0);
    std::cout << "Target key: " << std::endl;
    std::cin >> target;
    std::cout << "Money transaction created" << std::endl;
}
Transaction::clone
Transaction* clone() override;

This method is used to create a new object of the same class.

Example:
Transaction* MoneyTransaction::clone() {
    return new MoneyTransaction;
}

Database methods

Transaction::apply
std::vector<std::string> apply(Row* row) override;

This method is used to apply changes into a database. It returns a list of users to apply the reverse transaction

Example:
std::vector<std::string> MoneyTransaction::apply(Row* row){
    auto * cr = dynamic_cast<CustomRow*>(row);
    cr->money -= amount;
    std::vector<std::string> targets;
    targets.push_back(target);
    return  targets;
}
Transaction::apply_reverse
void apply_reverse(Row* row) override;

This method is used to apply the reverse transaction changes into a database.

Example:
void MoneyTransaction::apply_reverse(Row* row){
    auto * cr = dynamic_cast<CustomRow*>(row);
    cr->money += amount;
}
Transaction::createRow
Row* createRow() const override;

Creates a row in the database. Since Rows are custom objects from the developer, it cannot be generated by the framework, so the transactions are generating it.

Example:
Row* MoneyTransaction::createRow() const {
    return new CustomRow();
};
Transaction::validate
bool validate(Row *row) const override;

This method is used to check if the transaction is valid and can be add to the block chain

Example:
bool MoneyTransaction::validate(Row *row) const {
    auto * cr = dynamic_cast<CustomRow*>(row);
    return amount >= cr->money;
}

Reward

The reward is a very particular kind of Transaction. You only have to override the methods Reward::clone and Reward::createRow.

Database

The database is basically a map of Row. Rows are pure abstract classes that needs to be implemented, because their structure depends of your implementation
There are 3 methods that must be implemented.

Row::clone
virtual Row* clone() const = 0;

Like Transactions, you need to duplicate the Row in order to accept differents states on differents blocks in the block chain

Example:
Row* CustomRow::clone() const{
    auto r = new CustomRow;
    r->money = money;
    r->status = status;
    r->messages = messages;
    return r;
}
Row::to_string
virtual std::string to_string() const = 0;

Like Transactions, you need a visual representation of the database

Example:
std::string CustomRow::to_string() const {
    std::string str;
    str += "[" + status + "] (value: " + std::to_string(money) + ")\n";
    for(auto& a : messages)
        str += " - " + a + "\n";
    return str;
}
Row::reward
virtual void reward() = 0;

The reward method is called to reward the user who validated the block

Example:
void CustomRow::reward() {
    money += 1;
}

Advanced utilization

Proof

Proofs are the basis of block chain validation. Some proofs are implemented, but not all of them. Therefore, you can create your own proof and send it to the framework.

New proof

A new proof class needs to implements 2 methods
Proof::run
virtual void run(Block* block, std::string key) = 0;

This method will be called to generate the proof, and it will create a new Metadata for the current block.

Example:
void ProofOfWork::run(Block* block, std::string key){
    Hash tmp((block->parent_fingerprint != nullptr ? block->parent_fingerprint->to_string() : "0") + key);
    for(long long int i = 1; i > 0 ; i++){
        Hash t(&tmp, i);
        for(long long int j = 1; j > 0 ; j++) {
            Hash h(&t, j);
            if(h.to_string().substr(0, 1) == "0"){
                block->data = new ProofOfWorkMetadata(i, j, key);
                return;
            }
        }
    }
}
Proof::accept
virtual bool accept(Block* block, Message*) = 0;

This method will be called to check is the metadata generated by the method run by another peer is correct.
If it is not correct, it means the peer is generating false data, therefore the block must be rejected

Example:
bool ProofOfWork::accept(Block* block, Message* m){
    auto data = dynamic_cast<ProofOfWorkMetadata*>(block->data);
    Hash tmp((block->parent_fingerprint != nullptr ? block->parent_fingerprint->to_string() : "0") + data->winner);
    Hash t(&tmp, data->first);
    Hash h(&t, data->second);
    return h.to_string().substr(0, 1) == "0";
}

Feed the framework

Once your proof is done, you just have to give it to the framework using this method:
static void Proof::add_proof(int id, std::function<Proof*()> proof);

The id needs to be passed to the Config object. The second parameter is a lambda expression that creates the proof.

Example:
Proof::add_proof(Proof::WORK, []() -> Proof*{return new ProofOfWork;});

New metadata

If your proof require a new metadata class, you just have to provide this metadata to the serializer using this method.
void add_metadata(int id, std::function<Metadata*()> metadata);
Example:
serial->add_metadata(CustomMetadata::TYPE, []() -> Metadata*{return new CustomMetadata;});

Hash

Hash are the results of hashing function. There are some implemented hashing functions and you can create your own.

New Hash

A new hash class needs to implements 6 methods, but this method is always the same, with different parameters type
Hash::generate_hash
virtual std::string generate_hash(std::string hash1, double value) = 0;

virtual std::string generate_hash(std::string hash1, int value) = 0;

virtual std::string generate_hash(std::string hash1, long long int value) = 0;

virtual std::string generate_hash(std::string hash1, std::string value) = 0;

virtual std::string generate_hash(std::string value) = 0;

virtual std::string generate_hash(const Component* component, const Serializer* serializer, const char* encoding) = 0;

This method will be called to generate the hash according to the algorithm you chose.

Example:
std::string Hash_MD5::generate_hash(std::string hash1, std::string value){
    unsigned char digest[MD5_DIGEST_LENGTH + 1];
    std::string str = hash1 + value;
    MD5((const unsigned char*) str.c_str(), str.size(), digest);
    digest[MD5_DIGEST_LENGTH] = 0;
    return Encoding::toHexa(std::string((const char*)digest));
}

Feed the framework

Once your hash is done, you just have to give it to the framework using this method:
static void Hash::add_hash(int id, std::function<Hash*()> h);

The id needs to be passed to the Config object. The second parameter is a lambda expression that creates the hash.

Example:
Hash::add_hash(Hash::HASH_MD5, []() -> Hash*{return new Hash_MD5;});

Cryptography

The Cryptography objects are used to cipher transactions to assure their authenticity.
The framework provides the RSA cryptography, but it is possible to create new ones.

New Cryptography

A new Cryptography class needs to implement a few methods
Cryptography::set_public_key
virtual void set_public_key(std::string key) = 0;

This method will be called to register the given public public key.

Example:
void RSA_Cryptography::set_public_key(std::string key)
{
    size = 4096;
    BIO* bo = BIO_new_mem_buf( (void*) key.c_str(), (int) key.size());
    BIO_set_flags( bo, BIO_FLAGS_BASE64_NO_NL ) ; // NO NL
    rsa = PEM_read_bio_RSAPublicKey(bo, nullptr, nullptr, nullptr);
    BIO_free(bo);
}
Cryptography::encrypt
virtual std::string encrypt(std::string message) = 0;

This method will be called to encrypt a given text with the private key

Example:
std::string RSA_Cryptography::encrypt(std::string message) {
    auto encrypt = (unsigned char*)malloc((size_t) RSA_size(rsa));
    int encrypt_len;
    if((encrypt_len = RSA_private_encrypt((int)message.size()+1, (const unsigned char*)message.c_str(), encrypt, rsa, RSA_PKCS1_PADDING)) == -1) {
        std::cout << "\033[1;31m[ERR] ERROR ENCRYPT\033[0m" << std::endl;
    }
    std::string str;
    str.assign((const char*)encrypt, (unsigned long)encrypt_len);
    return str;
}
Cryptography::decrypt
virtual std::string decrypt(std::string message, int size) = 0;

This method will be called to decrypt a given text with the public key

Example:
std::string RSA_Cryptography::decrypt(std::string message, int size) {
    auto decrypt = (unsigned char*)malloc((size_t) size);
    if(RSA_public_decrypt((int)message.size(), (const unsigned char*) message.c_str(), decrypt, rsa, RSA_PKCS1_PADDING) == -1) {
        std::cout << "\033[1;31m[ERR] ERROR DECRYPT\033[0m" << std::endl;
    }
    return std::string((const char*)decrypt);
}
Cryptography::getPublicKey
virtual std::string getPublicKey() = 0;

This method will be called to send the public key of the creator to the other peers

Example:
std::string getKey(void (* fun)(BIO*, RSA*), RSA* rsa){
    int len;
    char   *key;
    BIO *p = BIO_new(BIO_s_mem());
    fun(p, rsa);
    len = BIO_pending(p);
    key = (char*)malloc((size_t) len + 1);
    BIO_read(p, key, len);
    key[len] = '\0';
    std::string final_key = key;
    free(key);
    return final_key;
}
std::string RSA_Cryptography::getPublicKey(){
    return getKey(public_action, rsa);
}

Feed the framework

Once your cryptography object is done, you just have to give it to the framework using this method:
static void Cryptography::add_cryptography(int id, std::function<Cryptography*()> crypto);

The id needs to be passed to the Config object. The second parameter is a lambda expression that creates the cryptography.

Example:
Cryptography::add_cryptography(Cryptography::CRYPTOGRAPHY_RSA, []() -> Hash*{return new RSA_Cryptography;});

Helpers

TransactionManager

The transaction manager is a built in class from the framework. You only need to give it a list of transactions, and then to give it to the Node object.

TransactionManager manager;
manager.put(new StatusTransaction);
manager.put(new MoneyTransaction);
manager.put(new MessagesTransaction);
node.start(manager);

The provided TransactionManager class allows you to easily run every type of transactions without implementing anything.
However, this class is a helper, you can easily manage the transactions creation by yourself without this class.
If you choose this solution, you only have to use the Node::request_transaction method in order to process your transactions.
It can also be replaced by the Framework helper.

Framework

In order to make the creation of the different tools easier, the framework provides an important (but not required) helper class, Framework.
This class only needs a list of Transaction as input and generates both TransactionManager and Serializer.

void Framework::add_transaction(Transaction* transaction);
Example:
Framework block_chain;
block_chain.add_transaction(new StatusTransaction);
block_chain.add_transaction(new MoneyTransaction);
block_chain.add_transaction(new MessagesTransaction);

//Get the manager
auto manager = block_chain.generate_manager();

//Get the serializer
auto serial = block_chain.generate_serializer();

TODO-List

Proof

For now, there is only one proof: the proof of work.
The framework will be updated with new proofs

Header files

For now, the installation of the framework is simple by copying files.
A complete version of the program will run with header files and DLL files.