how to initialize Empty array in stuct [Solidity]

I am suffering to initialize an empty array for struct when a struct is made.

I am suffering to initialize an empty array for struct when a struct is made.

pragma solidity ^0.5.1;

contract Board {

//storage
Post[] posts;

//struct
struct Post {
    address author;
    string title;
    string content;
    Comment[] comments;
}

struct Comment {
    address author;
    string comment;
}

//add-post
function addPost(address _author, string memory _title, string memory _content) public {
    posts.push(Post(_author, _title, _content, /* HERE IS THE PROBLEM POINT */));
}

}

I want to initialize the comments(struct member) with Empty Array(Type: Comment). Which code should I use for The Problem Point??

Learning Solidity

Solidity is a high-level language used to implement smart contracts. This is an object oriented language designed to target the Ethereum Virtual Machine. Let's explore it!

Introduction

Solidity is a high-level language used to implement smart contracts. This is an object oriented language designed to target the Ethereum Virtual Machine. Let's explore it!

Let's go!!!

Let's create a file called Contract.sol

First, you must define the version you are using. This is an information the compiler needs.

pragma solidity ^0.4.22;

All code in Ethereum belongs to a Contract. Let's create a contract and define a few variables inside it.

pragma solidity ^0.4.22;

contract DeveloperFactory {
// Let's create a Developer!
uint dnaDigits = 16;
uint ageDigits = 2;
}

Solidity is a typed language. uint stand for Unsigned Integer ( non negative integers ). These variables are state variables. They will be permanently stored in the contract storage ( in the Ethereum Blockchain ). Our Developer has a dna of 16 digits and an age of 2 digits.

Let's keep going!

Struct and arrays

pragma solidity ^0.4.22;

contract DeveloperFactory {
// Let's create a Developer!
uint dnaDigits = 16;
uint ageDigits = 2;

struct Developer {
    string name;
    uint dna;
    uint age;
}

The struct variable allows us to define more complex data structures. Here the Developer struc takes a string called name, a uint called dna and a uint called age.

Solidity also has arrays. You can create dynamic or fixed arrays. Here, our Developer array is dynamic because we do not specify a length. So we can keep adding Developers to our army without any limitations.

Developer[5] public developers is a fixed array that can contain 5 Developer struct.

Functions

A function would look something like this:

pragma solidity ^0.4.22;

contract DeveloperFactory {
// Let's create a Developer!
uint maxAge = 100;
uint minAge = 5;

struct Developer {
    string name;
    uint id;
    uint age;
}

Developer[] public developers;

function _createDeveloper( string _name, uint _id, uint _age ) private{
    developers.push( Developer( _name, _id, _age ) );
}

function _generateRandomId( string _str ) private pure returns (uint){
    uint rand = uint(keccak256(_str));
    return rand;
}

function createRandomDeveloper( string _name, uint _age ) public view {
    require(_age > minAge);
    require(_age < maxAge);
    uint randId = _generateRandomId( _name );
    _createDeveloper(_name, randId, _age );
}

}

We create functions with the function keyword. Functions can take parameters. By default, functions are public. I added the private keyword to make this function private. I also chose to add an underscore before a private function or variable to distinguish them from public variables. You don't have to do this, I just find it easier to read.

Ethereum has the hash function keccak256 built in. This is a version of SHA3. Pass it any string and you get a 256-bit hexadecimal number.

As you can see, we are typecasting the keccak256 value into a uint value and we return it.

Aside from the private keyword, there are several things you can add to a function:

  • The function returns something: Use the returns keyword and specify the type the function returns. Here, it will return a uint type.
  • The view keyword means that our function needs to look at some of our contract's variables, but not modify them. Our function createRandomDeveloper needs to look at minAge and maxAge variables.
  • The pure keyword means that the function is not accessing any data in the app. It only returns something depending on its parameters. Our _generateRandomId is pure.

We have three functions. _generateRandomId generates a random Id for our developer by using the keccak256 built in function. _createDeveloper creates and pushes a new Developer struct into our developers array. createRandomDeveloper is the only public function. It checks if the age provided is correct. The require statements will throw errors if it is not the case ( age greater than 100 and lower than 5 in our case ). So, this last function is the one that can be called from outside our contract.

Events

You can also create events so you can communicate what happens on the blockchain to your front-end app. Your app would then listen to those events and react accordingly.

pragma solidity ^0.4.22;

contract DeveloperFactory {
// Let's create a Developer!

event NewDeveloper(uint devId, string name, uint age);

uint maxAge = 100;
uint minAge = 5;

struct Developer {
    string name;
    uint id;
    uint age;
}

Developer[] public developers;

function _createDeveloper( string _name, uint _id, uint _age ) private{
    uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
    newDeveloper(id, _name, _age);
}

function _generateRandomId( string _str ) private pure returns (uint){
    uint rand = uint(keccak256(_str));
    return rand;
}

function createRandomDeveloper( string _name, uint _age ) public view {
    require(_age > minAge);
    require(_age < maxAge);
    uint randId = _generateRandomId( _name );
    _createDeveloper(_name, randId, _age );
}

}

Mappings

After struct and arrays, we can also store data in mappings. Mappings are a key-value store. For example:

mapping(uint => string) public keyUintStringValue;
mapping(address => uint) public addressToUint;

Here, we have two public mappings. The first one has a uint key and a stringvalue. The second has an address key and a uint value.

Addresses

The Ethereum blockchain is made up of addresses. Each account has an unique address. It takes the following form: 0x0cE440255306E921F41612C46F1v6df9Cc969183. Each address has a certain amount of Ether, which is the cryptocurrency used on the blockchain, and can receive or send Ether to other addresses.

With that in mind, let's create a new mapping for our DeveloperFactory:

pragma solidity ^0.4.22;

contract DeveloperFactory {

event NewDeveloper(uint devId, string name, uint age);

uint maxAge = 100;
uint minAge = 5;

struct Developer {
    string name;
    uint id;
    uint age;
}

Developer[] public developers;

mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;

function _createDeveloper( string _name, uint _id, uint _age ) private{
    uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
    NewDeveloper(id, _name, _age);
}

function _generateRandomId( string _str ) private pure returns (uint){
    uint rand = uint(keccak256(_str));
    return rand;
}

function createRandomDeveloper( string _name, uint _age ) public view {
    require(_age > minAge);
    require(_age < maxAge);
    uint randId = _generateRandomId( _name );
    _createDeveloper(_name, randId, _age );
}

}

In the first mapping, we will keep track of the number of devs each account ( address ) created. In the second, we will keep track of the owners for each dev.

msg.sender

Each contract is passive, they don't do anything until someone triggers them. msg.sender is a global variable that allows us to know which address is responsible for the triggering. It could be a account or another smart contract.

With that information, we can update our mappings appropriately. In the _createDeveloper function, we will increase the ownerDevCount for this particular address. In the devToOwner mapping, we will indicate that the newly created developer is owned by the msg.sender address.

pragma solidity ^0.4.22;

contract DeveloperFactory {

event NewDeveloper(uint devId, string name, uint age);

uint maxAge = 100;
uint minAge = 5;

struct Developer {
    string name;
    uint id;
    uint age;
}

Developer[] public developers;

mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;

function _createDeveloper( string _name, uint _id, uint _age ) private{
    uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
    ownerDevCount[msg.sender]++;
    devToOwner[id] = msg.sender;
    NewDeveloper(id, _name, _age);
}

function _generateRandomId( string _str ) private pure returns (uint){
    uint rand = uint(keccak256(_str));
    return rand;
}

function createRandomDeveloper( string _name, uint _age ) public view {
    require(_age > minAge);
    require(_age < maxAge);
    uint randId = _generateRandomId( _name );
    _createDeveloper(_name, randId, _age );
}

}

Notice the ++ notation to increase the ownerDevCount[msg.sender], just live Javascript!

Inheritance and import

Contracts can inherit from other contracts. If you open a new file call DeveloperLearning.sol, you can make it inherit from DeveloperFactory.sol:

pragma solidity ^0.4.22;

import "./DeveloperFactory.sol";

contract DeveloperLearning is DeveloperFactory {
// I have now access to DeveloperFactory functions
}

Notice how we imported the DeveloperFactory contract ( assuming it was in the same folder ). To declare inheritance, we use the keyword is.

payable

In order for a contract to receive ether, we need to have the payable keyword to a function. The amount sent will be accessible in the global variable msg.value. So we could make sure that a certain amount of ether is sent to the contract before the creation of a developer:

pragma solidity ^0.4.22;

contract DeveloperFactory {

event NewDeveloper(uint devId, string name, uint age);

uint maxAge = 100;
uint minAge = 5;

struct Developer {
    string name;
    uint id;
    uint age;
}

Developer[] public developers;

mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;

function _createDeveloper( string _name, uint _id, uint _age ) private{
    uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
    ownerDevCount[msg.sender]++;
    devToOwner[id] = msg.sender;
    NewDeveloper(id, _name, _age);
}

function _generateRandomId( string _str ) private pure returns (uint){
    uint rand = uint(keccak256(_str));
    return rand;
}

function createRandomDeveloper( string _name, uint _age ) public payable {
    require(_age > minAge);
    require(_age < maxAge);
    require(msg.value == 5);
    uint randId = _generateRandomId( _name );
    _createDeveloper(_name, randId, _age );
}

}

Memory and Storage

In Solidity, there are two places where variables are stored: in storage or in memory. A variable stored in memory is temporary, it exists while the function is used, then it is discarded. A variable stored in storage exists permanently on the blockchain. Most of the time, you don't have to worry about where to store your variables, as Solidity handles it for you.

For example, state variables ( maxAge, minAge, Developer ), declared outside of functions, are stored in storage. Variables like randId, id, rand are stored in memory.

But, in some cases, you want to explicitly declare where to store certain variables. Solidity allows you to do that with memory and storagekeywords.




What is in a block? - Blockchain

In my previous article, I tried to described the concept of a blockchain with code. This time, I'll try to describe the structure of a single block. I will use the Bitcoin blockchain to explain blocks, but keep in mind that the concepts will remain more or less the same. It could be useful to read my&nbsp;<a href="https://dev.to/damcosset/trying-to-understand-blockchain-by-making-one-ce4" target="_blank">last article</a>to understand a few things first.

Introduction

In my previous article, I tried to described the concept of a blockchain with code. This time, I'll try to describe the structure of a single block. I will use the Bitcoin blockchain to explain blocks, but keep in mind that the concepts will remain more or less the same. It could be useful to read my last articleto understand a few things first.

Structure of a block

A block is a container data structure. In the Bitcoin world, a block contains more than 500 transactions on average. The average size of a block seems to be 1MB (source). In Bitcoin Cash ( a hard fork from the Bitcoin blockchain ), the size of a block can go up to 8MB. This enables more transactions to be processed per second.

Anyway, a block is composed of a header and a long list of transactions. Let's start with the header.

Block Header

The header contains metadata about a block. There are three different sets of metadata:

  • The previous block hash. Remember that in a blockchain, every block is inherits from the previous block because we use the previous block's hash to create the new block's hash. For every block N, we feed it the hash of the block N-1.
  • Mining competition. For a block to be part of the blockchain, it needs to be given a valid hash. This contains the timestamp, the nonce and the difficulty. Mining is another crucial part of the blockchain technology, but it is outside the scope of this article.
  • The third part is a merkle tree root. This is a data structure to summarize the transactions in the block. And we will leave it at that for now. More on this later.
Block identifiers

To identify a block, you have a cryptographic hash, a digital signature if you will. This is created by hashing the block header twice with the SHA256 algorithm. For example, this is a block. I will refer to this block as an example for this article.

The block header hash for this particular block is (right column): 000000000000000000301fcfeb141088a93b77dc0d52571a1185b425256ae2fb

We also can see the previous block's hash (right column): 0000000000000000004b1ef0105dc1275b3adfd067aed63a43324929bed64fd7

Remember that we used the second hash to create the first. Every block uses the previous block's hash to construct its own hash. The block hash is a unique identifier. You won't find two blocks with the same hash.

The other way to identify a specific block is the block height. The is the position of the block in the blockchain. Our example's block is in the 500312 position. This means that there are 500311 blocks before this one. Since the creation of the Bitcoin blockchain in 2009, 500312 blocks have been created ( at the time of writing obviously ).

A block height is not unique. Several blocks can compete for the same position in the case of a fork, like Bitcoin Cash for example.

Merkle Trees

The transactions in a block are contained in a structure called a merkle tree or binary hash tree.

I feel that topics like that are easier to understand with actual examples. So we'll go coding for this. A merkle tree is constructed by recursively hashing pairs of nodes ( in this case, transactions ), until there is only one hash, called the root or merkle root. If we stay in the Bitcoin world, the cryptographic hash algorithm used is SHA256. This is applied twice each time.

An example: We have a block with 4 transactions. For the sake of simplicity, each transaction is a string:

const tA = 'Hello'
const tB = 'How are you?'
const tC = 'This is Thursday'
const tD = 'Happy new Year'

To construct our merkle tree, we start from the bottom. We take each transaction and double-hash them. I'll use the js-sha256 package here.

const sha256 = require('js-sha256').sha256

// Double-hashing here
const hA = sha256(sha256(tA))
const hB = sha256(sha256(tB))
const hC = sha256(sha256(tC))
const hD = sha256(sha256(tD))

//Results
52c87cd40ccfbd7873af4180fced6d38803d4c3684ed60f6513e8d16077e5b8e //hA
426436adcaca92d2f41d221e0dd48d1518b524c56e4e93fd324d10cb4ff8bfb9 //hB
6eeb307fb7fbc0b0fdb8bcfdcd2d455e4f6f347ff8007ed47475481a462e1aeb //hC
fd0df328a806a6517e2eafeaacea72964f689d29560185294b4e99ca16c63f8f //hD

Ok, great. Now remember that I wrote a merkle tree is constructed hashing pairs of nodes. So, we will pair our transactions and concatenate their hashes. Then, we will double hash them too. We will create a hash using the hashes hA and hB, and another for hC and hD. Then, we repeat that process until we have only one hash left and no more pairs to work with. The last hash will be our merkle root.

With only four transactions, this will be rather quick:

//Pairing hA and hB

const hAB = sha256(sha256(hA + hB))
//5dc23d1a2151665e2ac258340aa9a11ed227a4cc235e142a3e1738333575590b

//Pairing hC and hD

const hCD = sha256(sha256(hC + hD))
//ff220daefda29821435691a9aa07dd2c47ca1d2574b8b77344aa783142bae330

// We do it again. We pair hAB and hCD
// This is our root!
const hABCD = sha256(sha256(hAB + hCD))
//301faf21178234b04e1746ee43e126e7b2ecd2188e3fe6986356cc1dd7aa1377

The node at the top of the merkle tree is called the root. This is the information that is stored in the block header in each block on the blockchain. This is how transactions are summarized in each block. In our example block given earlier, the merkle root can be found in the right column: 

a89769d0487a29c73057e14d89afafa0c01e02782cba6c89b7018e5129d475cc

It doesn't matter how many transactions are contained in a block, they always will be summarized by a 32 bytes hash.

Note: The merkle tree is a binary tree. If there is an odd number of transactions, the last one will be duplicated so we can construct our tree.

Because all the leaves in our tree depends on other leaves, it is impossible to alter one leaf without altering others. If you change only one leaf ( one transaction ), the hash changes, therefore the hash you constructed by pairing it with another leaf changes, therefore the merkle root will be different.

You can prove that any transaction is included in a block by creating a authentification path or merkle path. You only need to know log base 2(N) 32-byte hashes. For example:

-For my 4 transactions merkle tree:

log base 2( 4 ) = 2 => If I have a path of 2 hashes for a tree of 4 transactions, I can manage to prove if a transaction belongs to this merkle tree.

For a 16 transactions merkle tree:

log base 2( 16 ) = 4 => If I have a path of 4 hashes for a tree of 16 transactions, I can manage to prove if a transaction belongs to this merkle tree.

log base 2( 1500 ) = 10.55 => If I have a path of 11 hashes for a tree of 1500 transactions, I can manage to prove if a transaction belongs to this merkle tree.

Perhaps a little diagram will help.

There are 16 leaves in this tree. We construct our tree from the bottom up by pairing each leaf. Now, anybody can prove that the leaf I ( in orange ) is part of this block by having the path given in green. We have only 4 hashes, but that is enough to know if the leaf I belongs here. That is because with those informations, we are able to construct every single leaf we need( in yellow ). We can create IJIJKLIJKLMNOP and the root and check if those hashes correspond. This is why it is very complicated to cheat a blockchain. To change one thing means you must change everything.

Well, that's pretty much what a block contains in the Bitcoin blockchain. Hope it helped!