Blocktron is a simple yet elegant and efficient blockchain framework written in Javascript for Node.js environment. Blocktron is aimed at developing generic, multipurpose blockchain platforms and softwares for various application use-cases, and also for educational and awareness purposes. This framework is built from the ground up using only opensource technologies.
blocktron-node is the single server node of the entire distributed blocktron system. A single node of the entire distributed system utilizes the blocktron-lib js blockchain library internally to build the blockchain core data structures and functionalities.
- About
- Motivation
- Build Status
- Technology Stack
- Why JavaScript & Node.js
- Blocktron & Byzantine Fault Tolerance (BFT)
- Getting Started
- Installation
- Dependency
- Changelog
- API Reference
- Adding Nodes
- Tests
- Continuous Integration
- Contributing
- Versioning
- Authors
- License
- Acknowledgements
The blockchain is an undeniably ingenious invention – the brainchild of a person or group of people known by the pseudonym, Satoshi Nakamoto
. But since then, it has evolved into something greater, and the main question every single person is asking is: What is Blockchain? By allowing digital information to be distributed but not copied, blockchain technology created the backbone of a new type of internet. Originally devised for the digital currency, Bitcoin, the tech community is now finding other potential uses for the technology.
As a computer engineer, I am passionate about solving different problems the world is facing today, through softwares, computing and other digital systems, to enhance the world as a better place for humanity. And blockchain has got a lot of attention in the recent years through the rise of certain Dapps platforms and obviously due to the hike in the value of bitcoins. Now the mission of Blocktron project is to contribute to that situation by developing blockchain technology to its next level and make it available to the world in its most simple and efficient form, so that the world can make the most of it.
This project has been set up with Travis-CI and Circle-CI where the continuous integration and continuous deployment tests are being run on every code commits to ensure code quality and code integrity. These badges shows the tests and builds passing or failing.
Blocktron project is built with the following technologies:
Read more about blocktron stack from here
Javascript, often refered as js, is a high level, multi-paradigm, Object-based, event-driven, interpreted programming language. It first appeared in 4th december 1995, 22 years ago. It's also charecterized as dynamic, weakly-typed and prototypal inheritance based language. It was exclusivley created for the web. Its one of the three core technologies which makes the world wide web. Javascript contributes to the interactions on the web pages. Node.js is the opensource, cross-platform javascript runtime environment that can execute javascript code outside the browser environment. It helps developers to build systems for the server side and command line applications using the regular javascript language. Unlike other programming languages Node.js (Javascript) is a lot different. Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world. Every browser has a JavaSript engine built in it to process JavaScript files contained in websites. Google Chrome uses V8 engine which is built using C++.
Node.js also uses this super-fast engine to interpret JavaScript files. Node.js uses an event-driven model. This means that Node.js waits for certain events to take place. It then acts on those events. Events can be anything from a click to a HTTP request. We can also declare our own custom events and make node.js listen for those events. Node.js uses a non-blocking I/O model. We know that I/O tasks take much longer than processing tasks. Node.js uses callback functions to handle such requests. V8 was first designed to increase the performance of JavaScript execution inside web browsers. In order to obtain speed, V8 translates JavaScript code into more efficient machine code instead of using an interpreter. It compiles JavaScript code into machine code at execution by implementing a JIT (Just-In-Time) compiler like a lot of modern JavaScript engines do such as SpiderMonkey or Rhino (Mozilla). The main difference here is that V8 doesn’t produce bytecode or any intermediate code. You can read more about V8 and its internals from the medium post by sessionstack.
Blockchains are inherently decentralized, distributed systems which consist of different actors (nodes) who act depending on their incentives and on the information that is available to them. Whenever a new transaction gets broadcasted across the network, nodes have the option to include that transaction to their copy of their ledger (blockchain) or to ignore it. When the majority of the actors which comprise the network decide on a single state, consensus is achieved.
A fundamental problem in distributed computing and multi-agent systems is to achieve overall system reliability in the presence of a number of faulty processes. This often requires processes to agree on some data value that is needed during computation. These processes are described as consensus. Now the questions arises:
- What happens when an actor decides to not follow the rules and to tamper with the state of his ledger?
- What happens when these actors are a large part of the network, but not the majority?
In order to create a secure consensus protocol, it must be fault tolerant.
Byzantine Fault Tolerance (BFT) is the characteristic which defines a system that tolerates the class of failures that belong to the Byzantine Generals’ Problem
. Byzantine Failure is the most difficult class of failure modes. It implies no restrictions, and makes no assumptions about the kind of behavior a node can have (e.g. a node can generate any kind of arbitrary data while posing as an honest actor). Byzantine Faults are the most severe and difficult to deal with. Byzantine Fault Tolerance has been needed in airplane engine systems, nuclear power plants and pretty much any system whose actions depend on the results of a large amount of sensors.
Blockchains are decentralized ledgers which, by definition, are not controlled by a central authority. Due to the value stored in these ledgers, bad actors have huge economic incentives to try and cause faults. That said, Byzantine Fault Tolerance, and thus a solution to the Byzantine Generals’ Problem for blockchains is much needed.
In the absence of BFT, a peer is able to transmit and post false transactions effectively nullifying the blockchain’s reliability. To make things worse, there is no central authority to take over and repair the damage. Inorder to address this issue, blocktron follows the standard consensus protocol called Proof Of Work (PoW). It's an opinionated, standardized, and universally approved blockchain consensus method to validate random blocks added to the blockchain. The Proof of work algorithm used in this framework is implemented in the blocktron-lib library as follows:
- Repeatedly hash the block data until it reaches the difficulty format: '0000'.
- Uses current block data as well as previous block hash.
- Continuously change the nonce until the correct hash is obtained.
- Return the nonce value which generates the correct hash. The proofOfWork algorithm runs to a complexity of
O(n)
.
Algorithm
ProofOfWork()
Input <previousBlockHash>, <currentBlockData>, DIFFICULTY
Output <nonce>
START
SET nonce = 0
GET hashString = hashBlock(previousBlockHash, currentBlockData, nonce)
DO nonce++
hashString = hashBlock(previousBlockHash, currentBlockData, nonce)
WHILE hashString.substring(0, DIFFICULTY) !== REPEAT('0') x DIFFICULTY
END
The result of this proof of work method is a number which is the nonce which generates the correct hash of the format '0000'. This nonce when used to generate a hash will generate the correctly formated hash, thus making it the proof we need. This is secure because, the algorithm has to run xxxx
(large number) times to generate the correct hash, in this case. This is a time consuming and resource intensive process, Thus when someone tries to tamper the integrity of a blockchain, they have to rebuild the entire blockchain using this proof of work algorithm to generate correct hash and nonce combination for the entire blockchain, which is impossible.
You can grab a copy of blocktron-node from github either by cloning or downloading the zip. clones can be created using the command:
git clone https://github.com/Blocktron-Project/blocktron-node.git
or using ssh as:
git clone ssh://[email protected]:Blocktron-Project/blocktron-node.git
You can install the project dependencies by running the following command inside the project folder:
npm i
Prerequisites
This project assumes you have the latest version of the following tools installed in your machine.
- Git
- Node.js >= v8.x
- Python
- Code editor(VS Code preferably or Atom)
- Terminal/cmd
This project is internally dependent on the following npm modules and packages:
-
blocktron-lib module is a member of the Blocktron Project. blocktron-lib is a javascript library housing a blockchain class, and the core blockchain data structures with various blockchain methods and functionalities. This library is fully extensible to accommodate any blockchain applications. Its completely independent and follows a class constructor design pattern and is written using ES6 specifications.
-
A tiny JavaScript debugging utility modelled after Node.js core's debugging technique. Works in Node.js and web browsers.
-
Fast, unopinionated, minimalist web framework for node.
-
Create HTTP errors for Express, Koa, Connect, etc. with ease.
-
The path module provides utilities for working with file and directory paths. It can be accessed using:
-
Extremely fast node.js logger, inspired by Bunyan. It also includes a shell utility to pretty-print its log files.
-
The simplified HTTP request client 'request' with Promise support. Powered by Bluebird.
-
Simple, fast generation of RFC4122 UUIDS
-
Babel compiler core.
-
babel-eslint allows you to lint ALL valid Babel code with the fantastic ESLint.
-
This package allows transpiling JavaScript files using Babel and webpack.
-
Babel preset for all es2015 plugins.
-
Babel preset for stage 0 plugins.
-
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions
-
A javascript testing framework from facebook
-
An API documentation generator for JavaScript.
-
Prettier is an opinionated code formatter. It enforces a consistent style by parsing your code and re-printing it with its own rules that take the maximum line length into account, wrapping code when necessary.
-
Automatically process your source files with Prettier when bundling via Webpack.
-
HTTP assertions made easy via superagent.
-
webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.
-
Webpack CLI encapsulates all code related to CLI handling. It captures options and sends them to webpack compiler. You can also find functionality for initializing a project and migrating between versions.
- v0.0.1
- Documentation updates
- Initial stable blocktron-node build
blocktron-node is a decentralized system. A simple distributed configuration will have atleast 2 nodes. But a single blocktron-node can be run by using the following npm command:
npm start
This command will run the blocktron-node from the compressed build distributable with development options and parameters on port 3000
. A log similar to the following will be printed to the console:
[2018-07-15T07:50:18.752Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron routes initialized
[2018-07-15T07:50:18.760Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron initialized and running in development mode
[2018-07-15T07:50:18.762Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron application middlewares initialized
[2018-07-15T07:50:18.766Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron custom middlewares initialized
[2018-07-15T07:50:18.769Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron routes chained to middlewares
[2018-07-15T07:50:18.789Z] INFO (blocktron/3152 on Sandeep-HP): Blocktron is running on port: 3000
Note: Logs are generated by Pino logger, it can be piped to logstash or any other log analysis programs of your choice.
You can access the API through a browser or a REST console like postman. When you hit the base url:
This is the index route of the blocktron-node instance. This route can be accessed by hitting http:127.0.0.1:3000/
. This route uses the HTTP GET
method.
Request header
Content-Type: application/json
in your REST client you will get the following response:
Status: 200 OK
Response headers
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"message": "Blocktron Node is running",
"port": 3000,
"status_code": 200,
"configuration": {
"process_title": "Blocktron Node",
"process_pid": 3152,
"memory": {
"resident_set_size": "46.890625 MB",
"heap_total": "34.33984375 MB",
"heap_used": "15.321807861328125 MB",
"external": "0.11514663696289062 MB"
},
"node_id": "576dd470881011e8889491bdff51cd6d",
"node_address": "http://127.0.0.1:3000",
"environment": "development",
"os": "win32",
"cpu_arch": "x64",
"process_versions": {
"node_version": "9.4.0",
"v8_version": "6.2.414.46-node.17"
}
}
}
This route gives the basic configuration information about the blocktron-node instance currently running on your machine. The response includes information like port on which the process is running, name of the process, process identifier, memory allocation and heap information, environment, operating system and platform information, and node.js information.
This route displays the code documentation. Detailed information about the blocktron-node codebase can be seen here.
This route responds with the blockchain data on the current node instance. This route uses the HTTP GET
method.
Request header
Content-Type: application/json
in your REST client you will get the following response:
Status: 200 OK
Response headers
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"chain": [{
"index": 1,
"timeStamp": 1531924923856,
"transactions": [],
"nonce": 1,
"hash": "0",
"previousHash": "0"
}],
"pendingTransactions": [],
"currentNodeUrl": "http://127.0.0.1:3001",
"networkNodes": []
}
By default every blocktron-node instance will respond with the very same blockchain. The chain
array in the response is the blockchain data. Every blockchain will have an initial block with index 1
. This block is called the Genesis Block. Consecutive blocks mined will be attached to the chain in chronological order. Each block in the chain will have the following key-values:
Key | Description | Default |
---|---|---|
Index | The chronological position of a block in the chain | 1 |
Timestamp | The unix epoch timestamp at the time of creation of the block | |
transactions | The array representing the list of transactions | [] |
nonce | The 'nonsense' number genearting the valid hash of the block data | 1 |
hash | The hash of the block's data | 0 |
previousHash | The hash of the previous block | 0 |
Note: The default values apply only for Genesis Block
The pendingTransactions
array will hold the list of transactions before being mined. currentNodeUrl
represents the address of the current node instance.
networkNodes
array holds the addressess of all the blocktron-nodes in the network.
This route is used to register a new node and then broadcasting it across the distributed system. This route uses the HTTP POST
method.
Request header
Content-Type: application/json
Request body
{
"newNodeUrl": "http://127.0.0.1:300x"
}
in your REST client you will get the following response:
Status: 201 Created
Response headers
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"status": "success",
"code": 201,
"message": "New nodes registered with the network"
}
When a node registration is successful, then newly registered node's network url will be added to all the nodes in the network, and urls of all other nodes wil be added to the networkNodes array of the newly added node. The following would be a sample blockchain representaion:
{
"chain": [{
"index": 1,
"timeStamp": 1531929909041,
"transactions": [],
"nonce": 1,
"hash": "0",
"previousHash": "0"
}],
"pendingTransactions": [],
"currentNodeUrl": "http://127.0.0.1:3001",
"networkNodes": [
"http://127.0.0.1:3002",
"http://127.0.0.1:3004",
"http://127.0.0.1:3003"
]
}
If the same request is received more than once, then the following response can be expected:
Status: 409 Conflict
Response headers
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"status": "resource conflict",
"code": 409,
"message": "Given node url: http://127.0.0.1:3002, is already present in registry or is a conflicting value"
}
Also the application logs the following line to console:
[2018-07-18T16:07:15.506Z] ERROR (blocktron/3284 on Sandeep-HP): Given url: http://127.0.0.1:3002 rejected, it is already present or is a conflicting value
If the network is unable to reach a particular node when registration happens, then the following response can be expected:
Status: 409 Conflict
Response headers
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"status": "resource conflict",
"code": 409,
"message": "Given node url: http://127.0.0.1:300x, is a conflicting value"
}
and the application logs the following line to the console:
[2018-07-18T16:18:48.488Z] ERROR (blocktron/6896 on Sandeep-HP): Nodes registration failed due to: RequestError: Error: connect ECONNREFUSED 127.0.0.1:300x
Note: ECONNREFUSED (Connection refused): No connection could be made because the target machine actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host.
This route is used to register a transaction and broadcast it among other nodes.
Request header
Content-Type: application/json
Request body
{
"amount": 50020,
"sender": "SANDEEP3J23NJ23N",
"receiver": "NEHANJK23NRJ2K3"
}
Once the request is served successfully, the following response can be expected.
Status: 201 Created
Response header
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"status": "success",
"code": 201,
"message": "Transaction created and broadcasted successfully"
}
Now the blockchain data will look similar to the following:
{
"chain": [{
"index": 1,
"timeStamp": 1532012395307,
"transactions": [],
"nonce": 1,
"hash": "0",
"previousHash": "0"
}],
"pendingTransactions": [{
"transactionId": "c9918db08b6411e8956069c0a2ff1a04",
"amount": 50020,
"sender": "SANDEEP3J23NJ23N",
"receiver": "NEHANJK23NRJ2K3"
}],
"currentNodeUrl": "http://127.0.0.1:3001",
"networkNodes": []
}
The newly added transaction will be added to the pendingTransactions array.
This route is used for mining the pending transactions and add them to the blockchain.
Request header
Content-Type: application/json
Once the request is served successfully, the following response can be expected.
Status: 201 Created
Response header
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Body
{
"status": "success",
"code": 201,
"message": "New block mined and broadcasted successfully",
"blockData": {
"index": 2,
"timeStamp": 1532015691848,
"transactions": [{
"transactionId": "f3ab95308b6b11e890bc5d646af74329",
"amount": 50020,
"sender": "SANDEEP3J23NJ23N",
"receiver": "NEHANJK23NRJ2K3"
}
],
"nonce": 110885,
"hash": "000017200806c32e29841ffd7d7f563b129f5e18a38a3e8e4ef489482fdcf9bb",
"previousHash": "0"
}
}
And the blockchain data will look similar to the following:
{
"chain": [{
"index": 1,
"timeStamp": 1532015588352,
"transactions": [],
"nonce": 1,
"hash": "0",
"previousHash": "0"
},
{
"index": 2,
"timeStamp": 1532015691848,
"transactions": [{
"transactionId": "f3ab95308b6b11e890bc5d646af74329",
"amount": 50020,
"sender": "SANDEEP3J23NJ23N",
"receiver": "NEHANJK23NRJ2K3"
}],
"nonce": 110885,
"hash": "000017200806c32e29841ffd7d7f563b129f5e18a38a3e8e4ef489482fdcf9bb",
"previousHash": "0"
}
],
"pendingTransactions": [{
"transactionId": "128a1c108b6c11e890bc5d646af74329",
"amount": 12.5,
"sender": "00BLOCKTRON",
"receiver": "00BLOCKTRON"
}],
"currentNodeUrl": "http://127.0.0.1:3001",
"networkNodes": [
"http://127.0.0.1:3002"
]
}
Now you can observe that the pendingTransactions array contains a transaction with amount 12.5 units. This is the mining reward provided by the Blocktron-node
This route can be used to validate the entire blockchain against all the chains in the network and reach consensus based on the Longest rule algorithm. This consensus approach is essential for a blockchain system or generally any distributed system to ensure Byzantine fault tolerance.
Request header
Content-Type: application/json
Response header
x-blocktron-Accept-Charset: UTF-8
x-blocktron-Accept-Language: en
x-blocktron-host-uuid: cd2ac140880711e8b6608d43a43507f6
x-blocktron-response-timestamp: 1531641455416
x-powered-by: blocktron
Assuming the consensus route is hit from a newly added node, The following response can be observed, if successful:
body
{
"status": "Chain replaced",
"code": 201,
"message": "Current blockchain has been replaced",
"blockchain": [
{
"index": 1,
"timeStamp": 1532099906418,
"transactions": [],
"nonce": 1,
"hash": "0",
"previousHash": "0"
},
{
"index": 2,
"timeStamp": 1532099983752,
"transactions": [],
"nonce": 18140,
"hash": "0000b9135b054d1131392c9eb9d03b0111d4b516824a03c35639e12858912100",
"previousHash": "0"
},
{
"index": 3,
"timeStamp": 1532099986120,
"transactions": [
{
"transactionId": "547ee0008c3011e88cf195a8aa7e7eda",
"amount": 12.5,
"sender": "00BLOCKTRON",
"receiver": "260b8c508c3011e88cf195a8aa7e7eda"
}
],
"nonce": 31235,
"hash": "00004d2fc19892ed16733a986f7c3b8f4874a2a1a3aaf8cd0f927bbcd4593a29",
"previousHash": "0000b9135b054d1131392c9eb9d03b0111d4b516824a03c35639e12858912100"
},
{
"index": 4,
"timeStamp": 1532099989254,
"transactions": [
{
"transactionId": "55d680c08c3011e88cf195a8aa7e7eda",
"amount": 12.5,
"sender": "00BLOCKTRON",
"receiver": "260b8c508c3011e88cf195a8aa7e7eda"
}
],
"nonce": 117330,
"hash": "00006509a6dcdb11f5f7b08b6c9eacfdeeed2975b13152774d4c1cd9e0edfa75",
"previousHash": "00004d2fc19892ed16733a986f7c3b8f4874a2a1a3aaf8cd0f927bbcd4593a29"
}
]
}
As represented the response says chain replaced
, which means the node has reached consensus with the distributed system. If the route is hit more than once, the response says Not Modified
.
The blocktron distributed system can have n
number of nodes in its network. Further nodes can be added by the following steps:
- Enter the new node to the
package.json
scripts object"node_x": "nodemon ./dist/main.min.js 300x http://127.0.0.1:300x"
- here
x
stands for the port number - its not neccessary to use
300x
series. Any valid port number can be used. nodemon
is not neccessary, but is helpful for development.
- New node can be run by the command
npm run node_x
. - Then use the Route: POST /registerAndBroadcastNode to register the new node to the network.
**Note: ** You must follow a consistent naming pattern for the nodes url, otherwise url conflicts could arise.
This project follows a Test Driven Development (TDD). Unit tests are written using Jest, an opensource testing and code coverage framework from facebook opensource. Test spec files can be found inside the test folder. Tests can be run by the command:
npm run test
Note: This includes code/test coverage report also
Continuous Integration services monitor repositories for changes, then automatically run unit tests on your behalf, typically in a containerized environment. To test this setup works in a continuous integration environment, an integration was done with Travis CI & CircleCI. According to the Travis Node.js Documentation, Travis automatically runs npm install
and npm test
. The only additional thing I had to add to the Travis configuration was to run npm run build
before running the tests. The working Travis config looks like this:
language: node_js
node_js:
- stable
install:
- npm install
script:
- npm run build-prod
- npm test
Here's the Travis build page for this project, which shows the tests passing.
CircleCI is similar to Travis-CI, but is more extensible and has much more control over the build process. The CircleCI config looks like this:
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:7.10
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
# run tests!
- run: npm run build-dev
- run: npm run build-prod
- run: npm test
Please read CONTRIBUTING.md for details on contributing to the project and CODE_OF_CONDUCT.md for the process for submitting pull requests to us.
We use SemVer for versioning. For the versions available, see the tags on this repository.
- Sandeep Vattapparambil - Founder, Lead Developer & Maintainer
See also the list of contributors who participates in this project.
The MIT License
Copyright (c) 2018- Sandeep Vattapparambil, http://www.sandeepv.in
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Made with ❤️ by Sandeep Vattapparambil.