Today, only the lazy one does not start another useless project on the blockchain; in this lesson I will tell you how to do something of practical use. As an example, take a package registry like npm using only a digital signature, a decentralized Swarm repository and Ethereum-based smart contracts.
Warning This is an evaluation version of a contract requiring an audit.
The code registry on the blockchain has a number of advantages over the usual one:
Consider each of the points separately:
This is an important point in building a trusted environment. The code must be signed by a developer you trust. In the case of the blockchain, this happens automatically. Today, if desired, any large repository can be attacked, and the code in it is replaced, you can hardly find out about it. The blockchain repository protects us from spoofing by storing a confirmed hash sum.
I think that everything here is clearly distributed storage enables global uncensored data access.
The package can be managed and owned by one or several participants or even an organization. Today, all accounts and data are in most cases registered for employees, and as a result, if you are dismissed, the transfer of rights to such resources can become a problem.
Tokenization allows a company owning a package to put it on a balance sheet, deposit it in intangible assets, sell it, and so is more pitiful. Also, when selling the company itself, tokenized assets will be easily taken into account and included in the price.
To implement such a solution, we will use a smart contract on the Ethereum blockchain and the Swarm distributed file system. For those who don't know, Swarm is a decentralized file storage like ipfs, only from the developers of Ethereum. Immediately, I note that the launch of Swarm is scheduled for the 4th quarter of this year and the code may change slightly, but the overall concept will remain the same.
Files in swarm are stored as directories. Each directory contains a manifest - a list of all files in the directory. The manifest is essentially JSON with an array of file paths and their hashes. Each directory is assigned a bzz-address, which is the top of the Merkel tree for the files listed in the manifest. To identify files, a hash sum protected from the attack by deleting a message is used ; to do this, before taking the hash sum, the length of the data is added to the beginning of the data as a 64-bit number with a byte order from the lowest to the highest.
The version is stored according to the principles of semver without named branches ( alpha
, rc0
, etc. are not supported). All packages are stored in the list, where the key is the package name and the value is the version tree. It looks like this:
// struct Package { address owner; // uint8 latestMajor; // mapping(uint8 => Major) majors; } // , . struct Major { // uint8 latestMinor; // mapping(uint8 => Minor) minors; } // , . struct Minor { // uint16 latestBuild; // . mapping(uint16 => Build) builds; } // bzz-. struct Build { // bzz- bytes32 bzz; // bool isPublished; }
Three contracts are used for the work of the contract:
// . mapping(bytes32 => Package) packages; // . mapping(bytes32 => bytes) names; // . mapping(bytes32 => address) transfers;
Package registration is necessary in order to link the package name and the owner, as well as convert the name into a hash. Name hashing is necessary in order to work with a package having a name of any length always take the same time / consume the same amount of gas:
function register(bytes _name) public returns(bytes32) { // , require(_name.length > 0); // bytes32 name = resolve(_name); // , require(packages[name].owner == address(0)); // packages[name] = Package(msg.sender, 0); // names[name] = _name; return name; }
Call from JS:
const Web3 = require('web3'); const {abi} = require('./contract.js'); const web3 = new Web3(new Web3('https://rinkeby.infura.io/')); // Initialize contract instance const reg = new web3.eth.Contract(abi, '0x57147069B117fD911Da6c43F3fBdC54a7A7D8C1d'); reg.methods.register('hello_world').send() .then((hash) => console.log(hash));
The data in Sqarm is loaded via the HTTP protocol, while to download the directory you can put the files in the tar archive:
tar -c * | curl -H "Content-Type: application/x-tar" --data-binary @- http://localhost:8500/bzz:/
As a result, we get a 32-bit top of the Merkel tree (bzz-address), for example:
1e0e21894d731271e50ea2cecf60801fdc8d0b23ae33b9e808e5789346e3355e
To get files from swarm you need to get a list of files :
curl -s http://localhost:8500/bzz-list:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/ | jq . > { "common_prefixes": [ "dir1/", "dir2/", "dir3/" ], "entries" : [ { "path": "file.txt", "contentType": "text/plain", "size": 9, "mod_time": "2017-03-12T15:19:55.112597383Z", "hash": "94f78a45c7897957809544aa6d68aa7ad35df695713895953b885aca274bd955" } ] }
For experiments, you can use the site https://swarm-gateways.org/ .
To register a new version, simply call the register method with the name hash, version numbers (major, minor, build) and bzz addresses.
function publish(bytes32 _package, uint8 _major, uint8 _minor, uint16 _build, bytes32 _bzz) public { Package storage package = packages[_package]; // . require(package.owner == msg.sender); // require(hasBuild(_package, _major, _minor, _build) == false); // package.majors[_major].minors[_minor].builds[_build] = Build(_bzz, true); Major memory major = package.majors[_major]; Minor memory minor = package.majors[_major].minors[_minor]; // if (package.latestMajor < _major) { package.latestMajor = _major; } // . if (major.latestMinor < _minor) { package.majors[_major].latestMinor = _minor; } // . if (minor.latestBuild < _build) { package.majors[_major].minors[_minor].latestBuild = _build; } emit Published(_package, _major, _minor, _build); }
reg.methods.publish(hash, 0, 1, 0, bzz).send();
As mentioned above, the ownership of the package can be transferred, methods that implement this functionality: transfer
and receive
. The transfer
method assigns a new owner, after which the new owner can receive the packet by calling the receive
method.
Until the call is receive
, the packet is kept by the current owner. This protects against erroneous actions, as well as requires confirmation by the host country, which allows you to make a transfer deal bilateral: no one can impose ownership on you without your consent.
Published
.Unpublished
event.This scheme excludes the possibility of recalling major or minor versions. Only certain builds can be recalled. It is also impossible to recall the latest build of the minor version. To do this, you must first publish a new one, and then recall the previous build. This eliminates the need to go through the version tree to reassign the latest version and thus reduces the amount of gas consumed.
In this form, all versions of packages become globally identifiable by hash, which allows, for example, to bind CVE numbers to specific versions of code. And due to the use of blockchain and distributed structure, changes in your code are guaranteed to reach users.
If you have any ideas on how to improve this code, leave issues or PR, watch for changes, well, put the stars, I would be grateful.
Answers to questions in the comments of StanislavL :
Why do I need to add versions with a number less than the current one?
Some versions of packages have LTS support, so the situation when the major and minor versions get into the release is quite likely.
There is no possibility of multiple ownership.
This functionality can be very different for each group of developers, so it must be implemented in a third-party contract, which will be the owner. This simplifies the registry code and eliminates binding to a single implementation.
What is the point of burning gas (= spending money) in the registration of the next build?
Semver assumes that the package code itself is versioned by builds, major and minor branches point to the interface. In other words, the contract code between adjacent builds can be rewritten to 100%, but the interface will remain the same.
There is an event eventTransfered(bytes32 indexed package)
;
but noOwnershipChanged
. I would add not only the package, but also the address of the candidate candidate.
The Transfered
event is OwnershipChanged
: it is called when the receiving party has called receive and the owner has changed. The candidate's address does not enter the log in order not to waste additional gas, since this data can be obtained from the transaction.
Source: https://habr.com/ru/post/353798/
All Articles