Only this pageAll pages
Powered by GitBook
1 of 47

Z-Docs

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Installation

Please refer to the installation method more applicable to you.

Our recommendation is to use the pre-built releases and verify the provided checksums.


Building from source

Prior to using go install make sure that you have Go >=1.17 installed and properly configured.

The stable branch is develop.

Copy


Using go install

Prior to using go install make sure that you have Go >=1.17 installed and properly configured.

go install github.com/ZChain-168168/ZChain-Blockchain/polygon-edge@develop

The binary will be available in your GOBIN environment variable, and will include the latest changes from the mainline develop branch.


Genesis files

Test-net and Main-net file in:

git clone https://github.com/ZChain-168168/ZChain-Blockchain/polygon-edge.git
cd polygon-edge/
go build main.go -o polygon-edge
sudo mv polygon-edge /usr/local/bin
https://github.com/ZChain-168168/ZChain-Blockchain

Tokenomics

Architecture

Minimal

Overview

As mentioned before, ZChains is a set of different modules, all connected to each other. The Blockchain is connected to the State, or for example, Synchronization, which pipes new blocks into the Blockchain.

Minimal is the cornerstone for these inter-connected modules. It acts as a central hub for all the services that run on the ZChains.

Startup Magic

Among other things, Minimal is responsible for:

  • Setting up data directories

  • Creating a keystore for libp2p communication

  • Creating storage

  • Setting up consensus

Additional Features

Modules

Setting up the blockchain object with GRPC, JSON RPC, and Synchronization
func NewServer(logger hclog.Logger, config *Config) (*Server, error) {
	m := &Server{
		logger: logger,
		config: config,
		chain:      config.Chain,
		grpcServer: grpc.NewServer(),
	}

	m.logger.Info("Data dir", "path", config.DataDir)

	// Generate all the paths in the dataDir
	if err := setupDataDir(config.DataDir, dirPaths); err != nil {
		return nil, fmt.Errorf("failed to create data directories: %v", err)
	}

	// Get the private key for the node
	keystore := keystore.NewLocalKeystore(filepath.Join(config.DataDir, "keystore"))
	key, err := keystore.Get()
	if err != nil {
		return nil, fmt.Errorf("failed to read private key: %v", err)
	}
	m.key = key

	storage, err := leveldb.NewLevelDBStorage(filepath.Join(config.DataDir, "blockchain"), logger)
	if err != nil {
		return nil, err
	}
	m.storage = storage

	// Setup consensus
	if err := m.setupConsensus(); err != nil {
		return nil, err
	}

	stateStorage, err := itrie.NewLevelDBStorage(filepath.Join(m.config.DataDir, "trie"), logger)
	if err != nil {
		return nil, err
	}

	st := itrie.NewState(stateStorage)
	m.state = st

	executor := state.NewExecutor(config.Chain.Params, st)
	executor.SetRuntime(precompiled.NewPrecompiled())
	executor.SetRuntime(evm.NewEVM())

	// Blockchain object
	m.blockchain, err = blockchain.NewBlockchain(logger, storage, config.Chain, m.consensus, executor)
	if err != nil {
		return nil, err
	}

	executor.GetHash = m.blockchain.GetHashHelper

	// Setup sealer
	sealerConfig := &sealer.Config{
		Coinbase: crypto.PubKeyToAddress(&m.key.PublicKey),
	}
	m.Sealer = sealer.NewSealer(sealerConfig, logger, m.blockchain, m.consensus, executor)
	m.Sealer.SetEnabled(m.config.Seal)

	// Setup the libp2p server
	if err := m.setupLibP2P(); err != nil {
		return nil, err
	}

	// Setup the GRPC server
	if err := m.setupGRPC(); err != nil {
		return nil, err
	}

	// Setup jsonrpc
	if err := m.setupJSONRPC(); err != nil {
		return nil, err
	}

	// Setup the syncer protocol
	m.syncer = protocol.NewSyncer(logger, m.blockchain)
	m.syncer.Register(m.libp2pServer.GetGRPCServer())
	m.syncer.Start()

	// Register the libp2p GRPC endpoints
	proto.RegisterHandshakeServer(m.libp2pServer.GetGRPCServer(), &handshakeService{s: m})

	m.libp2pServer.Serve()
	return m, nil
}

Consensus

Overview

The Consensus module provides an interface for consensus mechanisms.

Currently, the following consensus engines are available:

  • IBFT PoS

The Zchains wants to maintain a state of modularity and pluggability. This is why the core consensus logic has been abstracted away, so new consensus mechanisms can be built on top, without compromising on usability and ease of use.

Consensus Interface

The Consensus interface is the core of the mentioned abstraction.

  • The VerifyHeader method represents a helper function which the consensus layer exposes to the blockchain layer It is there to handle header verification

  • The Start method simply starts the consensus process, and everything associated with it. This includes synchronization, sealing, everything that needs to be done

  • The Close method closes the consensus connection

Consensus Configuration

There may be times when you might want to pass in a custom location for the consensus protocol to store data, or perhaps a custom key-value map that you want the consensus mechanism to use. This can be achieved through the Config struct, which gets read when a new consensus instance is created.

IBFT

ExtraData

The blockchain header object, among other fields, has a field called ExtraData. To review the fields present in the block header, please check out the section.

IBFT uses this extra field to store operational information regarding the block, answering questions like:

  • "Who signed this block?"

  • "Who are the validators for this block?"

These extra fields for IBFT are defined as follows:

Signing Data

In order for the node to sign information in IBFT, it leverages the signHash method:

Another notable method is the VerifyCommittedFields method, which verifies that the committed seals are from valid validators:

Snapshots

Snapshots, as the name implies, are there to provide a snapshot, or the state of a system at any block height (number).

Snapshots contain a set of nodes who are validators, as well as voting information (validators can vote for other validators). Validators include voting information in the Miner header filed, and change the value of the nonce:

  • Nonce is all 1s if the node wants to remove a validator

  • Nonce is all 0s if the node wants to add a validator

Snapshots are calculated using the processHeaders method:

This method is usually called with 1 header, but the flow is the same even with multiple headers. For each passed-in header, IBFT needs to verify that the proposer of the header is the validator. This can be done easily by grabbing the latest snapshot, and checking if the node is in the validator set.

Next, the nonce is checked. The vote is included, and tallied - and if there are enough votes a node is added/removed from the validator set, following which the new snapshot is saved.

Snapshot Store

The snapshot service manages and updates an entity called the snapshotStore, which stores the list of all available snapshots. Using it, the service is able to quickly figure out which snapshot is associated with which block height.

IBFT Startup

To start up IBFT, the Polygon Edge firstly needs to set up the IBFT transport:

It essentially creates a new topic with IBFT proto, with a new proto buff message. The messages are meant to be used by validators. The Polygon Edge then subscribes to the topic and handles messages accordingly.

MessageReq

The message exchanged by validators:

The View field in the MessageReq represents the current node position inside the chain. It has a round, and a sequence attribute.

  • round represents the proposer round for the height

  • sequence represents the height of the blockchain

The msgQueue filed in the IBFT implementation has the purpose of storing message requests. It orders messages by the View (firstly by sequence, then by round). The IBFT implementation also possesses different queues for different states in the system.

IBFT States

After the consensus mechanism is started using the Start method, it runs into an infinite loop which simulates a state machine:

SyncState

All nodes initially start in the Sync state.

This is because fresh data needs to be fetched from the blockchain. The client needs to find out if it's the validator, find the current snapshot. This state resolves any pending blocks.

After the sync finishes, and the client determines it is indeed a validator, it needs to transfer to AcceptState. If the client is not a validator, it will continue syncing, and stay in SyncState

AcceptState

The Accept state always check the snapshot and the validator set. If the current node is not in the validators set, it moves back to the Sync state.

On the other hand, if the node is a validator, it calculates the proposer. If it turns out that the current node is the proposer, it builds a block, and sends preprepare and then prepare messages.

  • Preprepare messages - messages sent by proposers to validators, to let them know about the proposal

  • Prepare messages - messages where validators agree on a proposal. All nodes receive all prepare messages

  • Commit messages - messages containing commit information for the proposal

If the current node is not a validator, it uses the getNextMessage method to read a message from the previously shown queue. It waits for the preprepare messages. Once it is confirmed everything is correct, the node moves to the Validate state.

ValidateState

The Validate state is rather simple - all nodes do in this state is read messages and add them to their local snapshot state.

Blockchain

Overview

One of the main modules of the ZChains are Blockchain and State.

Blockchain is the powerhouse that deals with block reorganizations. This means that it deals with all the logic that happens when a new block is included in the blockchain.

State represents the state transition object. It deals with how the state changes when a new block is included. Among other things, State handles:

  • Executing transactions

  • Executing the EVM

  • Changing the Merkle tries

  • Much more, which is covered in the corresponding State section 🙂

The key takeaway is that these 2 parts are very connected, and they work closely together in order for the client to function. For example, when the Blockchain layer receives a new block (and no reorganization occurred), it calls the State to perform a state transition.

Blockchain also has to deal with some parts relating to consensus (ex. is this ethHash correct?, is this PoW correct?). In one sentence, it is the main core of logic through which all blocks are included.

WriteBlocks

One of the most important parts relating to the Blockchain layer is the WriteBlocks method:

The WriteBlocks method is the entry point to write blocks into the blockchain. As a parameter, it takes in a range of blocks. Firstly, the blocks are validated. After that, they are written to the chain.

The actual state transition is performed by calling the processBlock method within WriteBlocks.

It is worth mentioning that, because it is the entry point for writing blocks to the blockchain, other modules (such as the Sealer) utilize this method.

Blockchain Subscriptions

There needs to be a way to monitor blockchain-related changes. This is where Subscriptions come in.

Subscriptions are a way to tap into blockchain event streams and instantly receive meaningful data.

The Blockchain Events contain information regarding any changes made to the actual chain. This includes reorganizations, as well as new blocks:

:::tip Refresher Do you remember when we mentioned the monitor command in the ?

The Blockchain Events are the original events that happen in Zchains, and they're later mapped to a Protocol Buffers message format for easy transfer. :::

State in Ethereum
CLI Commands
// Consensus is the interface for consensus
type Consensus interface {
	// VerifyHeader verifies the header is correct
	VerifyHeader(parent, header *types.Header) error

	// Start starts the consensus
	Start() error

	// Close closes the connection
	Close() error
}
// Config is the configuration for the consensus
type Config struct {
	// Logger to be used by the backend
	Logger *log.Logger

	// Params are the params of the chain and the consensus
	Params *chain.Params

	// Specific configuration parameters for the backend
	Config map[string]interface{}

	// Path for the consensus protocol to store information
	Path string
}
type IstanbulExtra struct {
	Validators    []types.Address
	Seal          []byte
	CommittedSeal [][]byte
}
func signHash(h *types.Header) ([]byte, error) {
	//hash := istambulHeaderHash(h)
	//return hash.Bytes(), nil

	h = h.Copy() // make a copy since we update the extra field

	arena := fastrlp.DefaultArenaPool.Get()
	defer fastrlp.DefaultArenaPool.Put(arena)

	// when hashign the block for signing we have to remove from
	// the extra field the seal and commitedseal items
	extra, err := getIbftExtra(h)
	if err != nil {
		return nil, err
	}
	putIbftExtraValidators(h, extra.Validators)

	vv := arena.NewArray()
	vv.Set(arena.NewBytes(h.ParentHash.Bytes()))
	vv.Set(arena.NewBytes(h.Sha3Uncles.Bytes()))
	vv.Set(arena.NewBytes(h.Miner.Bytes()))
	vv.Set(arena.NewBytes(h.StateRoot.Bytes()))
	vv.Set(arena.NewBytes(h.TxRoot.Bytes()))
	vv.Set(arena.NewBytes(h.ReceiptsRoot.Bytes()))
	vv.Set(arena.NewBytes(h.LogsBloom[:]))
	vv.Set(arena.NewUint(h.Difficulty))
	vv.Set(arena.NewUint(h.Number))
	vv.Set(arena.NewUint(h.GasLimit))
	vv.Set(arena.NewUint(h.GasUsed))
	vv.Set(arena.NewUint(h.Timestamp))
	vv.Set(arena.NewCopyBytes(h.ExtraData))

	buf := keccak.Keccak256Rlp(nil, vv)
	return buf, nil
}
func verifyCommitedFields(snap *Snapshot, header *types.Header) error {
	extra, err := getIbftExtra(header)
	if err != nil {
		return err
	}
	if len(extra.CommittedSeal) == 0 {
		return fmt.Errorf("empty committed seals")
	}

	// get the message that needs to be signed
	signMsg, err := signHash(header)
	if err != nil {
		return err
	}
	signMsg = commitMsg(signMsg)

	visited := map[types.Address]struct{}{}
	for _, seal := range extra.CommittedSeal {
		addr, err := ecrecoverImpl(seal, signMsg)
		if err != nil {
			return err
		}

		if _, ok := visited[addr]; ok {
			return fmt.Errorf("repeated seal")
		} else {
			if !snap.Set.Includes(addr) {
				return fmt.Errorf("signed by non validator")
			}
			visited[addr] = struct{}{}
		}
	}

	validSeals := len(visited)
	if validSeals <= 2*snap.Set.MinFaultyNodes() {
		return fmt.Errorf("not enough seals to seal block")
	}
	return nil
}
func (i *Ibft) processHeaders(headers []*types.Header) error {
	if len(headers) == 0 {
		return nil
	}

	parentSnap, err := i.getSnapshot(headers[0].Number - 1)
	if err != nil {
		return err
	}
	snap := parentSnap.Copy()

	saveSnap := func(h *types.Header) error {
		if snap.Equal(parentSnap) {
			return nil
		}

		snap.Number = h.Number
		snap.Hash = h.Hash.String()

		i.store.add(snap)

		parentSnap = snap
		snap = parentSnap.Copy()
		return nil
	}

	for _, h := range headers {
		number := h.Number

		validator, err := ecrecoverFromHeader(h)
		if err != nil {
			return err
		}
		if !snap.Set.Includes(validator) {
			return fmt.Errorf("unauthroized validator")
		}

		if number%i.epochSize == 0 {
			// during a checkpoint block, we reset the voles
			// and there cannot be any proposals
			snap.Votes = nil
			if err := saveSnap(h); err != nil {
				return err
			}

			// remove in-memory snaphots from two epochs before this one
			epoch := int(number/i.epochSize) - 2
			if epoch > 0 {
				purgeBlock := uint64(epoch) * i.epochSize
				i.store.deleteLower(purgeBlock)
			}
			continue
		}

		// if we have a miner address, this might be a vote
		if h.Miner == types.ZeroAddress {
			continue
		}

		// the nonce selects the action
		var authorize bool
		if h.Nonce == nonceAuthVote {
			authorize = true
		} else if h.Nonce == nonceDropVote {
			authorize = false
		} else {
			return fmt.Errorf("incorrect vote nonce")
		}

		// validate the vote
		if authorize {
			// we can only authorize if they are not on the validators list
			if snap.Set.Includes(h.Miner) {
				continue
			}
		} else {
			// we can only remove if they are part of the validators list
			if !snap.Set.Includes(h.Miner) {
				continue
			}
		}

		count := snap.Count(func(v *Vote) bool {
			return v.Validator == validator && v.Address == h.Miner
		})
		if count > 1 {
			// there can only be one vote per validator per address
			return fmt.Errorf("more than one proposal per validator per address found")
		}
		if count == 0 {
			// cast the new vote since there is no one yet
			snap.Votes = append(snap.Votes, &Vote{
				Validator: validator,
				Address:   h.Miner,
				Authorize: authorize,
			})
		}

		// check the tally for the proposed validator
		tally := snap.Count(func(v *Vote) bool {
			return v.Address == h.Miner
		})

		if tally > snap.Set.Len()/2 {
			if authorize {
				// add the proposal to the validator list
				snap.Set.Add(h.Miner)
			} else {
				// remove the proposal from the validators list
				snap.Set.Del(h.Miner)

				// remove any votes casted by the removed validator
				snap.RemoveVotes(func(v *Vote) bool {
					return v.Validator == h.Miner
				})
			}

			// remove all the votes that promoted this validator
			snap.RemoveVotes(func(v *Vote) bool {
				return v.Address == h.Miner
			})
		}

		if err := saveSnap(h); err != nil {
			return nil
		}
	}

	// update the metadata
	i.store.updateLastBlock(headers[len(headers)-1].Number)
	return nil
}
type snapshotStore struct {
	lastNumber uint64
	lock       sync.Mutex
	list       snapshotSortedList
}
func (i *Ibft) setupTransport() error {
	// use a gossip protocol
	topic, err := i.network.NewTopic(ibftProto, &proto.MessageReq{})
	if err != nil {
		return err
	}

	err = topic.Subscribe(func(obj interface{}) {
		msg := obj.(*proto.MessageReq)

		if !i.isSealing() {
			// if we are not sealing we do not care about the messages
			// but we need to subscribe to propagate the messages
			return
		}

		// decode sender
		if err := validateMsg(msg); err != nil {
			i.logger.Error("failed to validate msg", "err", err)
			return
		}

		if msg.From == i.validatorKeyAddr.String() {
			// we are the sender, skip this message since we already
			// relay our own messages internally.
			return
		}
		i.pushMessage(msg)
	})
	if err != nil {
		return err
	}

	i.transport = &gossipTransport{topic: topic}
	return nil
}
message MessageReq {
    // type is the type of the message
    Type type = 1;

    // from is the address of the sender
    string from = 2;

    // seal is the committed seal if message is commit
    string seal = 3;

    // signature is the crypto signature of the message
    string signature = 4;

    // view is the view assigned to the message
    View view = 5;

    // hash of the locked block
    string digest = 6;

    // proposal is the rlp encoded block in preprepare messages
    google.protobuf.Any proposal = 7;

    enum Type {
        Preprepare = 0;
        Prepare = 1;
        Commit = 2;
        RoundChange = 3;
    }
}

message View {
    uint64 round = 1;
    uint64 sequence = 2;
}
func (i *Ibft) start() {
	// consensus always starts in SyncState mode in case it needs
	// to sync with other nodes.
	i.setState(SyncState)

	header := i.blockchain.Header()
	i.logger.Debug("current sequence", "sequence", header.Number+1)

	for {
		select {
		case <-i.closeCh:
			return
		default:
		}

		i.runCycle()
	}
}

func (i *Ibft) runCycle() {
	if i.state.view != nil {
		i.logger.Debug(
		    "cycle", 
		    "state", 
		    i.getState(), 
		    "sequence", 
		    i.state.view.Sequence, 
		    "round", 
		    i.state.view.Round,
	    )
	}

	switch i.getState() {
	case AcceptState:
		i.runAcceptState()

	case ValidateState:
		i.runValidateState()

	case RoundChangeState:
		i.runRoundChangeState()

	case SyncState:
		i.runSyncState()
	}
}
// WriteBlocks writes a batch of blocks
func (b *Blockchain) WriteBlocks(blocks []*types.Block) error {
	if len(blocks) == 0 {
		return fmt.Errorf("no headers found to insert")
	}

	parent, ok := b.readHeader(blocks[0].ParentHash())
	if !ok {
		return fmt.Errorf("parent of %s (%d) not found: %s", blocks[0].Hash().String(), blocks[0].Number(), blocks[0].ParentHash())
	}

	// validate chain
	for i := 0; i < len(blocks); i++ {
		block := blocks[i]

		if block.Number()-1 != parent.Number {
			return fmt.Errorf("number sequence not correct at %d, %d and %d", i, block.Number(), parent.Number)
		}
		if block.ParentHash() != parent.Hash {
			return fmt.Errorf("parent hash not correct")
		}
		if err := b.consensus.VerifyHeader(parent, block.Header, false, true); err != nil {
			return fmt.Errorf("failed to verify the header: %v", err)
		}

		// verify body data
		if hash := buildroot.CalculateUncleRoot(block.Uncles); hash != block.Header.Sha3Uncles {
			return fmt.Errorf("uncle root hash mismatch: have %s, want %s", hash, block.Header.Sha3Uncles)
		}
		
		if hash := buildroot.CalculateTransactionsRoot(block.Transactions); hash != block.Header.TxRoot {
			return fmt.Errorf("transaction root hash mismatch: have %s, want %s", hash, block.Header.TxRoot)
		}
		parent = block.Header
	}

	// Write chain
	for indx, block := range blocks {
		header := block.Header

		body := block.Body()
		if err := b.db.WriteBody(header.Hash, block.Body()); err != nil {
			return err
		}
		b.bodiesCache.Add(header.Hash, body)

		// Verify uncles. It requires to have the bodies on memory
		if err := b.VerifyUncles(block); err != nil {
			return err
		}
		// Process and validate the block
		if err := b.processBlock(blocks[indx]); err != nil {
			return err
		}

		// Write the header to the chain
		evnt := &Event{}
		if err := b.writeHeaderImpl(evnt, header); err != nil {
			return err
		}
		b.dispatchEvent(evnt)

		// Update the average gas price
		b.UpdateGasPriceAvg(new(big.Int).SetUint64(header.GasUsed))
	}

	return nil
}
type Subscription interface {
    // Returns a Blockchain Event channel
	GetEventCh() chan *Event
	
	// Returns the latest event (blocking)
	GetEvent() *Event
	
	// Closes the subscription
	Close()
}
type Event struct {
	// Old chain removed if there was a reorg
	OldChain []*types.Header

	// New part of the chain (or a fork)
	NewChain []*types.Header

	// Difficulty is the new difficulty created with this event
	Difficulty *big.Int

	// Type is the type of event
	Type EventType

	// Source is the source that generated the blocks for the event
	// right now it can be either the Sealer or the Syncer. TODO
	Source string
}

State

To truly understand how State works, you must understand some basic Ethereum concepts.

We highly recommend reading the State in Ethereum guide.

Overview

Now that we've familiarized ourselves with basic Ethereum concepts, the next overview should be easy.

We mentioned that the World state trie has all the Ethereum accounts that exist. These accounts are the leaves of the Merkle trie. Each leaf has encoded Account State information.

This enables the Zchains to get a specific Merkle trie, for a specific point in time. For example, we can get the hash of the state at block 10.

The Merkle trie, at any point in time, is called a Snapshot.

We can have Snapshots for the state trie, or for the storage trie - they are basically the same. The only difference is in what the leaves represent:

  • In the case of the storage trie, the leaves contain an arbitrary state, which we cannot process or know what's in there

  • In the case of the state trie, the leaves represent accounts

The Snapshot interface is defined as such:

The information that can be committed is defined by the Object struct:

The implementation for the Merkle trie is in the state/immutable-trie folder. state/immutable-trie/state.go implements the State interface.

state/immutable-trie/trie.go is the main Merkle trie object. It represents an optimized version of the Merkle trie, which reuses as much memory as possible.

Executor

state/executor.go includes all the information needed for the Zchains to decide how a block changes the current state. The implementation of ProcessBlock is located here.

The apply method does the actual state transition. The executor calls the EVM.

Runtime

When a state transition is executed, the main module that executes the state transition is the EVM (located in state/runtime/evm).

The dispatch table does a match between the opcode and the instruction.

The core logic that powers the EVM is the Run loop.

This is the main entry point for the EVM. It does a loop and checks the current opcode, fetches the instruction, checks if it can be executed, consumes gas and executes the instruction until it either fails or stops.

Networking

Overview

A node has to communicate with other nodes on the network, in order to exchange useful information. To accomplish this task, the ZChains leverages the battle-tested libp2p framework.

The choice to go with libp2p is primarily focused on:

  • Speed - libp2p has a significant performance improvement over devp2p (used in GETH and other clients)

  • Extensibility - it serves as a great foundation for other features of the system

  • Modularity - libp2p is modular by nature, just like the Polygon Edge. This gives greater flexibility, especially when parts of the Polygon Edge need to be swappable

GRPC

On top of libp2p, the Polygon Edge uses the GRPC protocol. Technically, the Polygon Edge uses several GRPC protocols, which will be covered later on.

The GRPC layer helps abstract all the request/reply protocols and simplifies the streaming protocols needed for the Polygon Edge to function.

GRPC relies on Protocol Buffers to define services and message structures. The services and structures are defined in .proto files, which are compiled and are language-agnostic.

Earlier, we mentioned that the Polygon Edge leverages several GRPC protocols. This was done to boost the overall UX for the node operator, something which often lags with clients like GETH and Parity.

The node operator has a better overview of what is going on with the system by calling the GRPC service, instead of sifting through logs to find the information they're looking for.

GRPC for Node Operators

The following section might seem familiar because it was briefly covered in the section.

The GRPC service that is intended to be used by node operators is defined like so:

:::tip The CLI commands actually call the implementations of these service methods.

These methods are implemented in minimal/system_service.go. :::

GRPC for Other Nodes

The ZChains also implements several service methods that are used by other nodes on the network. The mentioned service is described in the section.

📜 Resources

Storage

Overview

The ZChains currently utilizes LevelDB for data storage, as well as an in-memory data store.

Throughout the ZChains, when modules need to interact with the underlying data store, they don't need to know which DB engine or service they're speaking to.

The DB layer is abstracted away between a module called Storage, which exports interfaces that modules query.

Each DB layer, for now only LevelDB, implements these methods separately, making sure they fit in with their implementation.

LevelDB

Prefixes

In order to make querying the LevelDB storage deterministic, and to avoid key storage clashing, the ZChains leverages prefixes and sub-prefixes when storing data

Future Plans

The plans for the near future include adding some of the most popular DB solutions, such as:

  • PostgreSQL

  • MySQL

📜 Resources

// Storage is a generic blockchain storage
type Storage interface {
	ReadCanonicalHash(n uint64) (types.Hash, bool)
	WriteCanonicalHash(n uint64, hash types.Hash) error

	ReadHeadHash() (types.Hash, bool)
	ReadHeadNumber() (uint64, bool)
	WriteHeadHash(h types.Hash) error
	WriteHeadNumber(uint64) error

	WriteForks(forks []types.Hash) error
	ReadForks() ([]types.Hash, error)

	WriteDiff(hash types.Hash, diff *big.Int) error
	ReadDiff(hash types.Hash) (*big.Int, bool)

	WriteHeader(h *types.Header) error
	ReadHeader(hash types.Hash) (*types.Header, error)

	WriteCanonicalHeader(h *types.Header, diff *big.Int) error

	WriteBody(hash types.Hash, body *types.Body) error
	ReadBody(hash types.Hash) (*types.Body, error)

	WriteSnapshot(hash types.Hash, blob []byte) error
	ReadSnapshot(hash types.Hash) ([]byte, bool)

	WriteReceipts(hash types.Hash, receipts []*types.Receipt) error
	ReadReceipts(hash types.Hash) ([]*types.Receipt, error)

	WriteTxLookup(hash types.Hash, blockHash types.Hash) error
	ReadTxLookup(hash types.Hash) (types.Hash, bool)

	Close() error
}

Community

CLI Commands
Protocol
Protocol Buffers
libp2p
gRPC
LevelDB
type State interface {
    // Gets a snapshot for a specific hash
	NewSnapshotAt(types.Hash) (Snapshot, error)
	
	// Gets the latest snapshot
	NewSnapshot() Snapshot
	
	// Gets the codeHash
	GetCode(hash types.Hash) ([]byte, bool)
}
type Snapshot interface {
    // Gets a specific value for a leaf
	Get(k []byte) ([]byte, bool)
	
	// Commits new information
	Commit(objs []*Object) (Snapshot, []byte)
}
// Object is the serialization of the radix object
type Object struct {
	Address  types.Address
	CodeHash types.Hash
	Balance  *big.Int
	Root     types.Hash
	Nonce    uint64
	Deleted  bool

	DirtyCode bool
	Code      []byte

	Storage []*StorageObject
}
func (t *Transition) apply(msg *types.Transaction) ([]byte, uint64, bool, error) {
	// check if there is enough gas in the pool
	if err := t.subGasPool(msg.Gas); err != nil {
		return nil, 0, false, err
	}

	txn := t.state
	s := txn.Snapshot()

	gas, err := t.preCheck(msg)
	if err != nil {
		return nil, 0, false, err
	}
	if gas > msg.Gas {
		return nil, 0, false, errorVMOutOfGas
	}

	gasPrice := new(big.Int).SetBytes(msg.GetGasPrice())
	value := new(big.Int).SetBytes(msg.Value)

	// Set the specific transaction fields in the context
	t.ctx.GasPrice = types.BytesToHash(msg.GetGasPrice())
	t.ctx.Origin = msg.From

	var subErr error
	var gasLeft uint64
	var returnValue []byte

	if msg.IsContractCreation() {
		_, gasLeft, subErr = t.Create2(msg.From, msg.Input, value, gas)
	} else {
		txn.IncrNonce(msg.From)
		returnValue, gasLeft, subErr = t.Call2(msg.From, *msg.To, msg.Input, value, gas)
	}
	
	if subErr != nil {
		if subErr == runtime.ErrNotEnoughFunds {
			txn.RevertToSnapshot(s)
			return nil, 0, false, subErr
		}
	}

	gasUsed := msg.Gas - gasLeft
	refund := gasUsed / 2
	if refund > txn.GetRefund() {
		refund = txn.GetRefund()
	}

	gasLeft += refund
	gasUsed -= refund

	// refund the sender
	remaining := new(big.Int).Mul(new(big.Int).SetUint64(gasLeft), gasPrice)
	txn.AddBalance(msg.From, remaining)

	// pay the coinbase
	coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(gasUsed), gasPrice)
	txn.AddBalance(t.ctx.Coinbase, coinbaseFee)

	// return gas to the pool
	t.addGasPool(gasLeft)

	return returnValue, gasUsed, subErr != nil, nil
}
func init() {
	// unsigned arithmetic operations
	register(STOP, handler{opStop, 0, 0})
	register(ADD, handler{opAdd, 2, 3})
	register(SUB, handler{opSub, 2, 3})
	register(MUL, handler{opMul, 2, 5})
	register(DIV, handler{opDiv, 2, 5})
	register(SDIV, handler{opSDiv, 2, 5})
	register(MOD, handler{opMod, 2, 5})
	register(SMOD, handler{opSMod, 2, 5})
	register(EXP, handler{opExp, 2, 10})

	...

	// jumps
	register(JUMP, handler{opJump, 1, 8})
	register(JUMPI, handler{opJumpi, 2, 10})
	register(JUMPDEST, handler{opJumpDest, 0, 1})
}

// Run executes the virtual machine
func (c *state) Run() ([]byte, error) {
	var vmerr error

	codeSize := len(c.code)
	
	for !c.stop {
		if c.ip >= codeSize {
			c.halt()
			break
		}

		op := OpCode(c.code[c.ip])

		inst := dispatchTable[op]
		
		if inst.inst == nil {
			c.exit(errOpCodeNotFound)
			break
		}
		
		// check if the depth of the stack is enough for the instruction
		if c.sp < inst.stack {
			c.exit(errStackUnderflow)
			break
		}
		
		// consume the gas of the instruction
		if !c.consumeGas(inst.gas) {
			c.exit(errOutOfGas)
			break
		}

		// execute the instruction
		inst.inst(c)

		// check if stack size exceeds the max size
		if c.sp > stackSize {
			c.exit(errStackOverflow)
			break
		}
		
		c.ip++
	}

	if err := c.err; err != nil {
		vmerr = err
	}
	
	return c.ret, vmerr
}
service System {
    // GetInfo returns info about the client
    rpc GetStatus(google.protobuf.Empty) returns (ServerStatus);

    // PeersAdd adds a new peer
    rpc PeersAdd(PeersAddRequest) returns (google.protobuf.Empty);

    // PeersList returns the list of peers
    rpc PeersList(google.protobuf.Empty) returns (PeersListResponse);

    // PeersInfo returns the info of a peer
    rpc PeersStatus(PeersStatusRequest) returns (Peer);

    // Subscribe subscribes to blockchain events
    rpc Subscribe(google.protobuf.Empty) returns (stream BlockchainEvent);
}
// Prefixes for the key-value store
var (
	// DIFFICULTY is the difficulty prefix
	DIFFICULTY = []byte("d")

	// HEADER is the header prefix
	HEADER = []byte("h")

	// HEAD is the chain head prefix
	HEAD = []byte("o")

	// FORK is the entry to store forks
	FORK = []byte("f")

	// CANONICAL is the prefix for the canonical chain numbers
	CANONICAL = []byte("c")

	// BODY is the prefix for bodies
	BODY = []byte("b")

	// RECEIPTS is the prefix for receipts
	RECEIPTS = []byte("r")

	// SNAPSHOTS is the prefix for snapshots
	SNAPSHOTS = []byte("s")

	// TX_LOOKUP_PREFIX is the prefix for transaction lookups
	TX_LOOKUP_PREFIX = []byte("l")
)

// Sub-prefixes
var (
	HASH   = []byte("hash")
	NUMBER = []byte("number")
	EMPTY  = []byte("empty")
)

Sealer

Overview

The Sealer is an entity that gathers the transactions, and creates a new block. Then, that block is sent to the Consensus module to seal it.

The final sealing logic is located within the Consensus module.

Run Method

:::caution Work in progress The Sealer and the Consensus modules will be combined into a single entity in the near future.

The new module will incorporate modular logic for different kinds of consensus mechanisms, which require different sealing implementations:

  • PoS (Proof of Stake)

  • PoA (Proof of Authority)

Currently, the Sealer and the Consensus modules work with PoW (Proof of Work). :::

Types

Overview

The Types module implements core object types, such as:

  • Address

Hash
  • Header

  • lots of helper functions

  • RLP Encoding / Decoding

    Unlike clients such as GETH, the ZChains doesn't use reflection for the encoding. The preference was to not use reflection because it introduces new problems, such as performance degradation, and harder scaling.

    The Types module provides an easy-to-use interface for RLP marshaling and unmarshalling, using the FastRLP package.

    Marshaling is done through the MarshalRLPWith and MarshalRLPTo methods. The analogous methods exist for unmarshalling.

    By manually defining these methods, the ZChains doesn't need to use reflection. In rlp_marshal.go, you can find methods for marshaling:

    • Bodies

    • Blocks

    • Headers

    • Receipts

    • Logs

    • Transactions

    Report an issue

    Overview

    Sometimes things break. It's always better to let the team know about any issues you might be encountering. On the ZChains GitHub page, you can file a new issue, and start discussing it with the team.

    Issue Template

    [Subject of the issue]

    Description

    Describe your issue in as much detail as possible here

    Your environment

    • OS and version

    • version of the ZChains

    • branch that causes this issue

    Steps to reproduce

    • Tell us how to reproduce this issue

    • Where the issue is, if you know

    • Which commands triggered the issue, if any

    Expected behaviour

    Tell us what should happen

    Actual behaviour

    Tell us what happens instead

    Logs

    Please paste any logs here that demonstrate the issue, if they exist

    Proposed solution

    If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it

    func (s *Sealer) run(ctx context.Context) {
    	sub := s.blockchain.SubscribeEvents()
    	eventCh := sub.GetEventCh()
    
    	for {
    		if s.config.DevMode {
    			// In dev-mode we wait for new transactions to seal blocks
    			select {
    			case <-s.wakeCh:
    			case <-ctx.Done():
    				return
    			}
    		}
    
    		// start sealing
    		subCtx, cancel := context.WithCancel(ctx)
    		done := s.sealAsync(subCtx)
    
    		// wait for the sealing to be done
    		select {
    		case <-done:
    			// the sealing process has finished
    		case <-ctx.Done():
    			// the sealing routine has been canceled
    		case <-eventCh:
    			// there is a new head, reset sealer
    		}
    
    		// cancel the sealing process context
    		cancel()
    
    		if ctx.Err() != nil {
    			return
    		}
    	}
    }

    Propose a new feature

    Overview

    If you want to include a fix, or just contribute to the code, it is highly encouraged for you to reach out to the team first. The ZChains uses a relatively basic feature proposition template, that is concise and to the point.

    PR Template

    Description

    Please provide a detailed description of what was done in this PR

    Changes include

    Breaking changes

    Please complete this section if any breaking changes have been made, otherwise delete it

    Checklist

    Testing

    Manual tests

    Please complete this section if you ran manual tests for this functionality, otherwise delete it

    Documentation update

    Please link the documentation update PR in this section if it's present, otherwise delete it

    Additional comments

    Please post additional comments in this section if you have them, otherwise delete it

    Configuration

    Concepts

    Set up AWS SSM (Systems Manager)

    Overview

    Currently, the ZChains is concerned with keeping 2 major runtime secrets:

    • The validator private key used by the node, if the node is a validator

    • The networking private key used by libp2p, for participating and communicating with other peers

    For additional information, please read through the

    The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.

    Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager, which of course is an abstraction.

    The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.

    This article details the necessary steps to get the ZChains up and running with .

    :::info previous guides It is highly recommended that before going through this article, articles on and are read. :::

    Prerequisites

    IAM Policy

    User needs to create an IAM Policy that allows read/write operations for AWS Systems Manager Parameter Store. After successfully creating IAM Policy, the user needs to attach it to the EC2 instance that is running the ZChains server. The IAM Policy should look something like this:

    More information on AWS SSM IAM Roles you can find in the .

    Required information before continuing:

    • Region (the region in which Systems Manager and nodes reside)

    • Parameter Path (arbitrary path that the secret will be placed in, for example /polygon-edge/nodes)

    Step 1 - Generate the secrets manager configuration

    In order for the ZChains to be able to seamlessly communicate with the AWS SSM, it needs to parse an already generated config file, which contains all the necessary information for secret storage on AWS SSM.

    To generate the configuration, run the following command:

    Parameters present:

    • PATH is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json

    • NODE_NAME is the name of the current node for which the AWS SSM configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node

    • REGION

    :::caution Node names Be careful when specifying node names.

    The Polygon Edge uses the specified node name to keep track of the secrets it generates and uses on the AWS SSM. Specifying an existing node name can have consequences of failing to write secret to AWS SSM.

    Secrets are stored on the following base path: SSM_PARAM_PATH/NODE_NAME :::

    Step 2 - Initialize secret keys using the configuration

    Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    :::info IAM Policy This step will fail if IAM Policy that allows read/write operations is not configured correctly and/or not attached to the EC2 instance running this command. :::

    Step 3 - Generate the genesis file

    The genesis file should be generated in a similar manner to the and guides, with minor changes.

    Since AWS SSM is being used instead of the local file system, validator addresses should be added through the --ibft-validator flag:

    Step 4 - Start the Polygon Edge client

    Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server command.

    The server command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config flag:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    is the region in which the AWS SSM resides. This has to be the same region as the node utilizing AWS SSM.
  • SSM_PARAM_PATH is the name of the path that the secret will be stored in. For example if --name node1 and ssm-parameter-path=/polygon-edge/nodes are specified, the secret will be stored as /polygon-edge/nodes/node1/<secret_name>

  • Managing Private Keys Guide
    AWS Systems Manager Parameter Store
    Local Setup
    Cloud Setup
    AWS docs
    Local Setup
    Cloud Setup
    {
      "Version": "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : [
            "ssm:PutParameter",
            "ssm:DeleteParameter",
            "ssm:GetParameter"
          ],
          "Resource" : [
            "arn:aws:ssm:<aws_region>:<aws_account_id>:parameter<ssm-parameter-path>*"
          ]
        }
      ]
    }
    polygon-edge secrets generate --type aws-ssm --dir <PATH> --name <NODE_NAME> --extra region=<REGION>,ssm-parameter-path=<SSM_PARAM_PATH>
    polygon-edge secrets init --config <PATH>
    polygon-edge genesis --ibft-validator <VALIDATOR_ADDRESS> ...
    polygon-edge server --secrets-config <PATH> ...

    Proof of Stake

    Overview

    This section aims to give a better overview of some concepts currently present in the Proof of Stake (PoS) implementation of the ZChains.

    The ZChains Proof of Stake (PoS) implementation is meant to be an alternative to the existing PoA IBFT implementation, giving node operators the ability to easily choose between the two when starting a chain.

    PoS Features

    The core logic behind the Proof of Stake implementation is situated within the .

    This contract is pre-deployed whenever a PoS mechanism Zchains chain is initialized, and is available on the address 0x0000000000000000000000000000000000001001 from block 0.

    Epochs

    Epochs are a concept introduced with the addition of PoS to the Zchains.

    Epochs are considered to be a special time frame (in blocks) in which a certain set of validators can produce blocks. Their lengths are modifiable, meaning node operators can configure the length of an epoch during genesis generation.

    At the end of each epoch, an epoch block is created, and after that event a new epoch starts. To learn more about epoch blocks, see the section.

    Validator sets are updated at the end of each epoch. Nodes query the validator set from the Staking Smart Contract during the creation of the epoch block, and save the obtained data to local storage. This query and save cycle is repeated at the end of each epoch.

    Essentially, it ensures that the Staking Smart Contract has full control over the addresses in the validator set, and leaves nodes with only 1 responsibility - to query the contract once during an epoch for fetching the latest validator set information. This alleviates the responsibility from individual nodes from taking care of validator sets.

    Staking

    Addresses can stake funds on the Staking Smart Contract by invoking the stake method, and by specifying a value for the staked amount in the transaction:

    By staking funds on the Staking Smart Contract, addresses can enter the validator set and thus be able to participate in the block production process.

    :::info Threshold for staking Currently, the minimum threshold for entering the validator set is staking 1 ETH :::

    Unstaking

    Addresses that have staked funds can only unstake all of their staked funds at once.

    Unstaking can be invoked by calling the unstake method on the Staking Smart Contract:

    After unstaking their funds, addresses are removed from the validator set on the Staking Smart Contract, and will not be considered validators during the next epoch.

    Epoch Blocks

    Epoch Blocks are a concept introduced in the PoS implementation of IBFT in Zchains.

    Essentially, epoch blocks are special blocks that contain no transactions and occur only at the end of an epoch. For example, if the epoch size is set to 50 blocks, epoch blocks would be considered to be blocks 50, 100 , 150 and so on.

    They are used to performing additional logic that shouldn't occur during regular block production.

    Most importantly, they are an indication to the node that it needs to fetch the latest validator set information from the Staking Smart Contract.

    After updating the validator set at the epoch block, the validator set (either changed or unchanged) is used for the subsequent epochSize - 1 blocks, until it gets updated again by pulling the latest information from the Staking Smart Contract.

    Epoch lengths (in blocks) are modifiable when generating the genesis file, by using a special flag --epoch-size:

    The default size of an epoch is 100000 blocks in the Zchains.

    Contract pre-deployment

    The Zchains pre-deploys the during genesis generation to the address 0x0000000000000000000000000000000000001001.

    It does so without a running EVM, by modifying the blockchain state of the Smart Contract directly, using the passed in configuration values to the genesis command.

    Set up and use Proof of Stake (PoS)

    Overview

    This guide goes into detail on how to set up a Proof of Stake network with the ZChains, how to stake funds for nodes to become validators and how to unstake funds.

    It is highly encouraged to read and go through the Local Setup / Cloud Setup sections, before going along with this PoS guide. These sections outline the steps needed to start a Proof of Stake (PoS) cluster with the ZChains.

    Currently, there is no limit to the number of validators that can stake funds on the Staking Smart Contract.

    Staking Smart Contract

    The repo for the Staking Smart Contract is located .

    It holds the necessary testing scripts, ABI files and most importantly the Staking Smart Contract itself.

    Setting up an N node cluster

    Setting up a network with the ZChains is covered in the / sections.

    The only difference between setting up a PoS and PoA cluster is in the genesis generation part.

    When generating the genesis file for a PoS cluster, an additional flag is needed --pos:

    Setting the length of an epoch

    Epochs are covered in detail in the section.

    To set the size of an epoch for a cluster (in blocks), when generating the genesis file, an additional flag is specified --epoch-size:

    This value specified in the genesis file that the epoch size should be 50 blocks.

    The default value for the size of an epoch (in blocks) is 100000.

    :::info Lowering the epoch length As outlined in the section, epoch blocks are used to update the validator sets for nodes.

    The default epoch length in blocks (100000) may be a long time to way for validator set updates. Considering that new blocks are added ~2s, it would take ~55.5h for the validator set to possibly change.

    Setting a lower value for the epoch length ensures that the validator set is updated more frequently. :::

    Using the Staking Smart Contract scripts

    Prerequisites

    The Staking Smart Contract repo is a Hardhat project, which requires NPM.

    To initialize it correctly, in the main directory run:

    Setting up the provided helper scripts

    Scripts for interacting with the deployed Staking Smart Contract are located on the .

    Create an .env file with the following parameters in the Smart Contracts repo location:

    Where the parameters are:

    • JSONRPC_URL - the JSON-RPC endpoint for the running node

    • PRIVATE_KEYS - private keys of the staker address

    • STAKING_CONTRACT_ADDRESS - the address of the staking smart contract ( default 0x0000000000000000000000000000000000001001)

    Staking funds

    :::info Staking address The Staking Smart Contract is pre-deployed at address 0x0000000000000000000000000000000000001001.

    Any kind of interaction with the staking mechanism is done through the Staking Smart Contract at the specified address.

    To learn more about the Staking Smart Contract, please visit the section. :::

    In order to become part of the validator set, an address needs to stake a certain amount of funds above a threshold.

    Currently, the default threshold for becoming part of the validator set is 1 ETH.

    Staking can be initiated by calling the stake method of the Staking Smart Contract, and specifying a value >= 1 ETH.

    After the .env file mentioned in the has been set up, and a chain has been started in PoS mode, staking can be done with the following command in the Staking Smart Contract repo:

    The stake Hardhat script stakes a default amount of 1 ETH, which can be changed by modifying the scripts/stake.ts file.

    If the funds being staked are >= 1 ETH, the validator set on the Staking Smart Contract is updated, and the address will be part of the validator set starting from the next epoch.

    Unstaking funds

    Addresses that have a stake can only unstake all of their funds at once.

    After the .env file mentioned in the has been set up, and a chain has been started in PoS mode, unstaking can be done with the following command in the Staking Smart Contract repo:

    Fetching the list of stakers

    All addresses that stake funds are saved to the Staking Smart Contract.

    After the .env file mentioned in the has been set up, and a chain has been started in PoS mode, fetching the list of validators can be done with the following command in the Staking Smart Contract repo:

    Staking Smart Contract
    Epoch Blocks
    Staking Smart Contract
    here
    Local Setup
    Cloud Setup
    Epoch Blocks
    Epoch Blocks
    Staking Smart Contract repo
    Staking Smart Contract
    previous section
    previous section
    previous section
    const StakingContractFactory = await ethers.getContractFactory("Staking");
    let stakingContract = await StakingContractFactory.attach(STAKING_CONTRACT_ADDRESS)
    as
    Staking;
    stakingContract = stakingContract.connect(account);
    
    const tx = await stakingContract.stake({value: STAKE_AMOUNT})
    const StakingContractFactory = await ethers.getContractFactory("Staking");
    let stakingContract = await StakingContractFactory.attach(STAKING_CONTRACT_ADDRESS)
    as
    Staking;
    stakingContract = stakingContract.connect(account);
    
    const tx = await stakingContract.unstake()
    polygon-edge genesis --epoch-size 50 ...
    polygon-edge genesis --pos ...
    polygon-edge genesis --epoch-size 50
    npm install
    JSONRPC_URL=http://localhost:10002
    PRIVATE_KEYS=0x0454f3ec51e7d6971fc345998bb2ba483a8d9d30d46ad890434e6f88ecb97544
    STAKING_CONTRACT_ADDRESS=0x0000000000000000000000000000000000001001
    npm run stake
    npm run unstake
    npm run info

    Backup/restore node instance

    Overview

    This guide goes into detail on how to back up and restore a ZChains node instance. It covers the base folders and what they contain, as well as which files are critical for performing a successful backup and restore.

    Base folders

    ZChains leverages LevelDB as its storage engine. When starting a ZChains node, the following sub-folders are created in the specified working directory:

    • blockchain - Stores the blockchain data

    • trie - Stores the Merkle tries (world state data)

    • keystore - Stores private keys for the client. This includes the libp2p private key and the sealing/validator private key

    • consensus - Stores any consensus information that the client might need while working. For now, it stores the node's private validator key

    It is critical for these folders to be preserved in order for the ZChains instance to run smoothly.

    Create backup from a running node and restore for new node

    This section guides you through creating archive data of the blockchain in a running node and restoring it in another instance.

    Backup

    backup command fetches blocks from a running node by gRPC and generates an archive file. If --from and --to are not given in the command, this command will fetch blocks from genesis to latest.

    Restore

    A server imports blocks from an archive at the start when starting with --restore flag. Please make sure that there is a key for new node. To find out more about importing or generating keys, visit the .

    Back up/Restore Whole data

    This section guides you through backup the data including state data and key and restoring into the new instance.

    Step 1: Stop the running client

    Since the ZChains uses LevelDB for data storage, the node needs to be stopped for the duration of the backup, as LevelDB doesn't allow for concurrent access to its database files.

    Additionally, the ZChains also does data flushing on close.

    The first step involves stopping the running client (either through a service manager or some other mechanism that sends a SIGINT signal to the process), so it can trigger 2 events while gracefully shutting down:

    • Running data flush to disk

    • Release of the DB files lock by LevelDB

    Step 2: Backup the directory

    Now that the client is not running, the data directory can be backed up to another medium. Keep in mind that the files with a .key extension contain the private key data that can be used to impersonate the current node, and they should never be shared with a third/unknown party.

    :::info Please back up and restore the generated genesis file manually, so the restored node is fully operational. :::

    Restore

    Step 1: Stop the running client

    If any instance of the ZChains is running, it needs to be stopped in order for step 2 to be successful.

    Step 2: Copy the backed up data directory to the desired folder

    Once the client is not running, the data directory which was previously backed up can be copied over to the desired folder. Additionally, restore the previously copied genesis file.

    Step 3: Run the ZChains client while specifying the correct data directory

    In order for the Polygon Edge to use the restored data directory, at launch, the user needs to specify the path to the data directory. Please consult the section on information regarding the data-dir flag.

    Secret Managers section
    CLI Commands
    $ polygon-edge backup --grpc-address 127.0.0.1:9632 --out backup.dat [--from 0x0] [--to 0x100]
    $ polygon-edge server --restore archive.dat

    Get started

    Tokenomics

    ZToken ($ZCD} Distribution Overview

    The ZChains introduces a comprehensive tokenomics framework designed to support network operations, governance, and growth. With a total supply of 15 billion ZCD, the distribution is meticulously planned to ensure long-term viability and community engagement.

    Total and Maximum Supply

    • Amount: 15 Billion ZCD (100.00%)

    • Both total and maximum supplies are capped at 15 billion ZCD, ensuring a predictable inflation rate and safeguarding value.

    Circulating Supply

    • Amount: Available Soon

    • Fully Diluted Market Cap: Available Soon

    • The circulating supply represents the number of tokens currently available to the public and actively traded.

    Token Distribution Breakdown

    • Liquidity and Exchange Listings The majority of tokens are allocated to liquidity pools on top centralized exchanges to ensure ample market liquidity and facilitate easy trading.

      • Allocation: 10 Billion ZCD (66.67%)

      • Purpose: Supports liquidity on centralized exchanges, facilitating broader market access and reducing price volatility.

    Overview

    Introducing ZChains

    An interoperable L1 network driven by its ecosystem and focused in delivering experience-based services. ZChains is a modular and extensible framework for building Ethereum-compatible blockchain networks, sidechains, and general scaling solutions.

    Its primary use is to bootstrap a new blockchain network while providing full compatibility with Ethereum smart contracts and transactions. It uses IBFT (Istanbul Byzantine Fault Tolerant) consensus mechanism, supported in one flavour as PoS (proof of stake).

    ZChains also supports communication with multiple blockchain networks, enabling transfers of both ERC-20 and ERC-721 tokens, by utilising the centralised bridge solution.

    Industry standard wallets can be used to interact with ZChains through the JSON-RPC endpoints and node operators can perform various actions on the nodes through the protocol.

    To find out more about ZChains, visit the:

    ---

    CAUTION

    This is a work in progress so architectural changes may happen in the future, so please contact the ZChains team if you would like to use it in production.

    To get started by running a polygon-edge network locally, please read: and .

    Local Setup

    :::caution This guide is for testing purposes only

    The below guide will instruct you on how to set up a ZChains network on your local machine for testing and development purposes.

    The procedure differs greatly from the way you would want to set up ZChains network for a real use scenario on a cloud provider:

    :::

    Requirements

    Refer to to install ZChains.

    Glossary

    Blockchain: A system of recording information in a way that makes it difficult or impossible to change, hack, or cheat the system. A blockchain is essentially a digital ledger of transactions that is duplicated and distributed across the entire network of computer systems on the blockchain.

    Ethereum Virtual Machine (EVM): A computation engine that acts as a decentralized computer, containing millions of executable projects. It serves as the runtime environment for every smart contract on the Ethereum network.

    Proof of Stake (PoS): A consensus mechanism in a blockchain network where transactions and blocks are validated by approved accounts, known as validators based on the amount of cryptocurrency locked up, or staked. PoS is known for its speed and efficiency compared to proof-of-work systems.

    Smart Contract: Self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. The code and the agreements contained therein exist across a distributed, decentralized blockchain network.

    Tokenomics: A term that combines 'token' and 'economics' and refers to the economics concerning the creation, distribution, and usage of a cryptocurrency or blockchain token.

    Validator: A participant in the blockchain network responsible for verifying, voting on, and maintaining the ledger's accuracy and helping the network reach consensus.

    Marketing
    • Allocation: 2 Billion ZCD (13.33%)

    • Purpose: Funds the ecosystem's growth through investments in new projects and community initiatives.

  • Team Holding

    • Allocation: 2 Billion ZCD (13.33%)

    • Purpose: The allocation for the team is structured with a 12-month lock, followed by linear vesting over a 60-month period. This extended vesting period aligns with our vision for sustained collaboration and dedication.

  • Foundation

    • Allocation: 1 Billion ZCD (6.67%)

    • Purpose: Reserved for governance activities and long term initiatives.

  • Decentralized Applications (dApps): Digital applications or programs that exist and run on a blockchain or peer-to-peer network of computers instead of a single computer, and are outside the purview and control of a single authority.

    Liquidity Pool: A collection of funds locked in a smart contract. Liquidity pools are used to facilitate decentralized trading, lending, and many more functions.

    gRPC
    Official Website
    GitHub
    Telegram
    X (Twitter)
    Instagram
    Discord
    Installation
    Local Setup
    Overview
    Local Setup

    In this guide, our goal is to establish a working polygon-edge blockchain network working with IBFT consensus protocol. The blockchain network will consist of 4 nodes of whom all 4 are validator nodes, and as such are eligible for both proposing block, and validating blocks that came from other proposers. All 4 nodes will run on the same machine, as the idea of this guide is to give you a fully functional IBFT cluster in the least amount of time.

    To achieve that, we will guide you through 4 easy steps:

    1. Initializing data directories will generate both the validator keys for each of the 4 nodes, and initialize empty blockchain data directories. The validator keys are important as we need to bootstrap the genesis block with the initial set of validators using these keys.

    2. Preparing the connection string for the bootnode will be the vital information for every node we will run as to which node to connect to when starting for the first time.

    3. Generating the genesis.json file will require as input both the validator keys generated in step 1 used for setting the initial validators of the network in the genesis block and the bootnode connection string from step 2.

    4. Running all the nodes is the end goal of this guide and will be the last step we do, we will instruct the nodes which data directory to use and where to find the genesis.json which bootstraps the initial network state.

    As all four nodes will be running on localhost, during the setup process it is expected that all the data directories for each of the nodes are in the same parent directory.

    :::info Number of validators

    There is no minimum to the number of nodes in a cluster, which means clusters with only 1 validator node are possible. Keep in mind that with a single node cluster, there is no crash tolerance and no BFT guarantee.

    The minimum recommended number of nodes for achieving a BFT guarantee is 4 - since in a 4 node cluster, the failure of 1 node can be tolerated, with the remaining 3 functioning normally.

    :::

    Step 1: Initialize data folders for IBFT and generate validator keys

    In order to get up and running with IBFT, you need to initialize the data folders, one for each node:

    Each of these commands will print the validator key and the node ID. You will need the Node ID of the first node for the next step.

    Step 2: Prepare the multiaddr connection string for the bootnode

    For a node to successfully establish connectivity, it must know which bootnode server to connect to in order to gain information about all the remaining nodes on the network. The bootnode is sometimes also known as the rendezvous server in p2p jargon.

    bootnode is not a special instance of the polygon-edge node. Every polygon-edge node can serve as a bootnode, but every polygon-edge node needs to have a set of bootnodes specified which will be contacted to provide information on how to connect with all remaining nodes in the network.

    To create the connection string for specifying the bootnode, we will need to conform to the multiaddr format:

    In this guide, we will treat the first and second nodes as the bootnodes for all other nodes. What will happen in this scenario is that nodes that connect to the node 1 or node 2 will get information on how to connect to one another through the mutually contacted bootnode.

    :::info You need to specify at least one bootnode to start a node

    At least one bootnode is required, so other nodes in the network can discover each other. More bootnodes are recommended, as they provide resilience to the network in case of outages. In this guide we will list two nodes, but this can be changed on the fly, with no impact on the validity of the genesis.json file. :::

    Since we are running on localhost, it is safe to assume that the <ip_address> is 127.0.0.1.

    For the <port> we will use 10001 since we will configure the libp2p server for node 1 to listen on this port later.

    And lastly, we need the <node_id> which we can get from the output of the previously ran command polygon-edge secrets init --data-dir test-chain-1 command (which was used to generate keys and data directories for the node1)

    After the assembly, the multiaddr connection string to the node 1 which we will use as the bootnode will look something like this (only the <node_id> which is at the end should be different):

    Similarly, we construct the multiaddr for second bootnode as shown below

    Step 3: Generate the genesis file with the 4 nodes as validators

    What this command does:

    • The --ibft-validators-prefix-path sets the prefix folder path to the one specified which IBFT in Zchains can use. This directory is used to house the consensus/ folder, where the validator's private key is kept. The validator's public key is needed in order to build the genesis file - the initial list of bootstrap nodes. This flag only makes sense when setting up the network on localhost, as in a real-world scenario we cannot expect all the nodes' data directories to be on the same filesystem from where we can easily read their public keys.

    • The --bootnode sets the address of the bootnode that will enable the nodes to find each other. We will use the multiaddr string of the node 1, as mentioned in step 2.

    The result of this command is the genesis.json file which contains the genesis block of our new blockchain, with the predefined validator set and the configuration for which node to contact first in order to establish connectivity.

    :::info Premining account balances

    You will probably want to set up your blockchain network with some addresses having "premined" balances.

    To achieve this, pass as many --premine flags as you want per address that you want to be initialized with a certain balance on the blockchain.

    For example, if we would like to premine 1000 ETH to address 0x3956E90e632AEbBF34DEB49b71c28A83Bc029862 in our genesis block, then we would need to supply the following argument:

    Note that the premined amount is in WEI, not ETH.

    :::

    :::info Set the block gas limit

    The default gas limit for each block is 5242880. This value is written in the genesis file, but you may want to increase / decrease it.

    To do so, you can use the flag --block-gas-limit followed by the desired value as shown below :

    :::

    :::info Set system file descriptor limit

    The default file descriptor limit ( maximum number of open files ) on some operating systems is pretty small. If the nodes are expected to have high throughput, you might consider increasing this limit on the OS level.

    For Ubuntu distro the procedure is as follows ( if you're not using Ubuntu/Debian distro, check the official docs for your OS ) :

    • Check current os limits ( open files )

    • Increase open files limit

      • Localy - affects only current session:

      • Globaly or per user ( add limits at the end of /etc/security/limits.conf file ) :

      Optionaly, modify additional parameters, save the file and restart the system. After restart check file descriptor limit again. It should be set to the value you defined in limits.conf file. :::

    Step 4: Run all the clients

    Because we are attempting to run a Polygon Edge network consisting of 4 nodes all on the same machine, we need to take care to avoid port conflicts. This is why we will use the following reasoning for determining the listening ports of each server of a node:

    • 10000 for the gRPC server of node 1, 20000 for the GRPC server of node 2, etc.

    • 10001 for the libp2p server of node 1, 20001 for the libp2p server of node 2, etc.

    • 10002 for the JSON-RPC server of node 1, 20002 for the JSON-RPC server of node 2, etc.

    To run the first client (note the port 10001 since it was used as a part of the libp2p multiaddr in step 2 alongside node 1's Node ID):

    To run the second client:

    To run the third client:

    To run the fourth client:

    To briefly go over what has been done so far:

    • The directory for the client data has been specified to be ./test-chain-*

    • The GRPC servers have been started on ports 10000, 20000, 30000 and 40000, for each node respectively

    • The libp2p servers have been started on ports 10001, 20001, 30001 and 40001, for each node respectively

    • The JSON-RPC servers have been started on ports 10002, 20002, 30002 and 40002, for each node respectively

    • The seal flag means that the node which is being started is going to participate in block sealing

    • The chain flag specifies which genesis file should be used for chain configuration

    The structure of the genesis file is covered in the CLI Commands section.

    After running the previous commands, you have set up a 4 node Polygon Edge network, capable of sealing blocks and recovering from node failure.

    :::info Start the client using config file

    Instead of specifying all configuration parameters as CLI arguments, the Client can also be started using a config file by executing the following command:

    Example:

    Currently, we only support json based configuration file, sample config file can be found here

    :::

    :::info Steps to run a non-validator node

    A Non-validator will always sync the latest blocks received from the validator node, you can start a non-validator node by running the following command.

    For example, you can add fifth Non-validator client by executing the following command :

    :::

    :::info Specify the price limit A Polygon Edge node can be started with a set price limit for incoming transactions.

    The unit for the price limit is wei.

    Setting a price limit means that any transaction processed by the current node will need to have a gas price higher then the set price limit, otherwise it will not be included in a block.

    Having the majority of nodes respect a certain price limit enforces the rule that transactions in the network cannot be below a certain price threshold.

    The default value for the price limit is 0, meaning it is not enforced at all by default.

    Example of using the --price-limit flag:

    It is worth noting that price limits are enforced only on non-local transactions, meaning that the price limit does not apply to transactions added locally on the node. :::

    Step 5: Interact with the polygon-edge network

    Now that you've set up at least 1 running client, you can go ahead and interact with the blockchain using the account you premined above and by specifying the JSON-RPC URL to any of the 4 nodes:

    • Node 1: http://localhost:10002

    • Node 2: http://localhost:20002

    • Node 3: http://localhost:30002

    • Node 4: http://localhost:40002

    Follow this guide to issue operator commands to the newly built cluster: How to query operator information (the GRPC ports for the cluster we have built are 10000/20000/30000/40000 for each node respectively)

    Cloud Setup
    Installation

    Network stress testing

    Prerequisites

    This guide assumes that:

    • You have a working ZChains network up and running

    • Both your JSON-RPC and GRPC endpoints are reachable

    Overview

    The ZChains Loadbot is a helper utility that is meant to stress test a ZChains network.

    Currently, it supports 2 modes:

    • transfer - mode that does stress testing using fund-transfer transactions. [Default].

    • deploy - mode that deploys specified smart contracts with each transaction.

    Transfer Mode

    The transfer mode assumes that there is a sender account that has initial funds to conduct the loadbot run.

    The sender's account address and private key need to be set in the environment variables:

    Deploy Mode

    The deploy mode conducts contract deployment with each new transaction in the loadbot run. The contract being deployed can be specified using , or if the contract path is omitted, a default Greeter.sol is used instead.

    Terminology

    This section covers some basic terminology regarding the loadbot configuration.

    • count - The number of transactions to be submitted in the specified mode

    • tps - The number of transactions that should be submitted to the node per second

    Start the loadbot

    As an example, here is a valid command you can use to run the loadbot using two premined accounts:

    You should get a result similar to this on your terminal :

    JSON RPC

    Overview

    The JSON RPC module implements the JSON RPC API layer, something that dApp developers use to interact with the blockchain.

    It includes support for standard , as well as websocket endpoints.

    Cloud Setup

    :::info This guide is for mainnet or testnet setups

    The below guide will instruct you on how to set up a ZChains network on a cloud provider for a production setup of your testnet or mainnet.

    If you would like to setup a ZChains network locally to quickly test the polygon-edge before doing a production-like setup, please refer to :::

    Requirements

    Refer to to install ZChains.

    ulimit -u 65535
    sudo vi /etc/security/limits.conf  # we use vi, but you can use your favorite text editor
    # /etc/security/limits.conf
    #
    #Each line describes a limit for a user in the form:
    #
    #<domain>        <type>  <item>  <value>
    #
    #Where:
    #<domain> can be:
    #        - a user name
    #        - a group name, with @group syntax
    #        - the wildcard *, for default entry
    #        - the wildcard %, can be also used with %group syntax,
    #                 for maxlogin limit
    #        - NOTE: group and wildcard limits are not applied to root.
    #          To apply a limit to the root user, <domain> must be
    #          the literal username root.
    #
    #<type> can have the two values:
    #        - "soft" for enforcing the soft limits
    #        - "hard" for enforcing hard limits
    #
    #<item> can be one of the following:
    #        - core - limits the core file size (KB)
    #        - data - max data size (KB)
    #        - fsize - maximum filesize (KB)
    #        - memlock - max locked-in-memory address space (KB)
    #        - nofile - max number of open file descriptors
    #        - rss - max resident set size (KB)
    #        - stack - max stack size (KB)
    #        - cpu - max CPU time (MIN)
    #        - nproc - max number of processes
    #        - as - address space limit (KB)
    #        - maxlogins - max number of logins for this user
    
    #        - maxsyslogins - max number of logins on the system
    #        - priority - the priority to run user process with
    #        - locks - max number of file locks the user can hold
    #        - sigpending - max number of pending signals
    #        - msgqueue - max memory used by POSIX message queues (bytes)
    #        - nice - max nice priority allowed to raise to values: [-20, 19]
    #        - rtprio - max realtime priority
    #        - chroot - change root to directory (Debian-specific)
    #
    #<domain>      <type>  <item>         <value>
    #
    
    #*               soft    core            0
    #root            hard    core            100000
    #*               hard    rss             10000
    #@student        hard    nproc           20
    #@faculty        soft    nproc           20
    #@faculty        hard    nproc           50
    #ftp             hard    nproc           0
    #ftp             -       chroot          /ftp
    #@student        -       maxlogins       4
    
    *               soft    nofile          65535
    *               hard    nofile          65535
    
    # End of file
    polygon-edge secrets init --data-dir test-chain-1
    polygon-edge secrets init --data-dir test-chain-2
    polygon-edge secrets init --data-dir test-chain-3
    polygon-edge secrets init --data-dir test-chain-4
    /ip4/<ip_address>/tcp/<port>/p2p/<node_id>
    /ip4/127.0.0.1/tcp/10001/p2p/16Uiu2HAmJxxH1tScDX2rLGSU9exnuvZKNM9SoK3v315azp68DLPW
    /ip4/127.0.0.1/tcp/20001/p2p/16Uiu2HAmS9Nq4QAaEiogE4ieJFUYsoH28magT7wSvJPpfUGBj3Hq 
    polygon-edge genesis --consensus ibft --ibft-validators-prefix-path test-chain- --bootnode /ip4/127.0.0.1/tcp/10001/p2p/16Uiu2HAmJxxH1tScDX2rLGSU9exnuvZKNM9SoK3v315azp68DLPW --bootnode /ip4/127.0.0.1/tcp/20001/p2p/16Uiu2HAmS9Nq4QAaEiogE4ieJFUYsoH28magT7wSvJPpfUGBj3Hq 
    --premine=0x3956E90e632AEbBF34DEB49b71c28A83Bc029862:1000000000000000000000
    const (
    	GenesisFileName       = "./genesis.json"
    	DefaultChainName      = "example"
    	DefaultChainID        = 100
    	DefaultPremineBalance = "0x3635C9ADC5DEA00000"
    	DefaultConsensus      = "pow"
    	GenesisGasUsed        = 458752
    	GenesisGasLimit       = 5242880 // The default block gas limit
    )
    --block-gas-limit 1000000000
    ubuntu@ubuntu:~$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 15391
    max locked memory       (kbytes, -l) 65536
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 15391
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :10001 --jsonrpc :10002 --seal
    polygon-edge server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :20001 --jsonrpc :20002 --seal
    polygon-edge server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30001 --jsonrpc :30002 --seal
    polygon-edge server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :40001 --jsonrpc :40002 --seal
    polygon-edge server --config <config_file_path>
    polygon-edge server --config ./test/config-node1.json
    polygon-edge server --data-dir <directory_path> --chain <genesis_filename> --grpc-address <portNo> --libp2p <portNo> --jsonrpc <portNo>
    polygon-edge server --data-dir ./test-chain --chain genesis.json --grpc-address :50000 --libp2p :50001 --jsonrpc :50002 
    polygon-edge server --price-limit 100000 ...

    Explorer

    Overview

    This guide goes into details on how to using Explorer instance to work with ZChains.

    ZChains Block Explorer: https://scan.zchains.com/

    Secret Managers

    Consensus

    specific flags
    contract
    Blockchain Interface

    TheZchains uses the blockchain interface to define all the methods that the JSON RPC module needs to use, in order to deliver its endpoints.

    The blockchain interface is implemented by the Minimal server. It is the base implementation that's passed into the JSON RPC layer.

    ETH Endpoints

    All the standard JSON RPC endpoints are implemented in:

    Filter Manager

    The Filter Manager is a service that runs alongside the JSON RPC server.

    It provides support for filtering blocks on the blockchain. Specifically, it includes both a log and a block level filter.

    The Filter Manager relies heavily on Subscription Events, mentioned in the Blockchain section

    Filter Manager events get dispatched in the Run method:

    📜 Resources

    • Ethereum JSON-RPC

    json-rpc endpoints
    # Example
    export LOADBOT_0x9A2E59d06899a383ef47C1Ec265317986D026055=154c4bc0cca942d8a0b49ece04d95c872d8f53d34b8f2ac76253a3700e4f1151
    Zchains loadbot  --jsonrpc http://127.0.0.1:10002 --grpc-address 127.0.0.1:10000 --sender 0x9A2E59d06899a383ef47C1Ec265317986D026055 --count 2000 --value 0x100 --tps 100
    =====[LOADBOT RUN]=====
    
    [COUNT DATA]
    Transactions submitted = 2000
    Transactions failed    = 0
    
    [TURN AROUND DATA]
    Average transaction turn around = 3.490800s
    Fastest transaction turn around = 2.002320s
    Slowest transaction turn around = 5.006770s
    Total loadbot execution time    = 24.009350s
    
    [BLOCK DATA]
    Blocks required = 11
    
    Block #223 = 120 txns
    Block #224 = 203 txns
    Block #225 = 203 txns
    Block #226 = 202 txns
    Block #227 = 201 txns
    Block #228 = 199 txns
    Block #229 = 200 txns
    Block #230 = 199 txns
    Block #231 = 201 txns
    Block #232 = 200 txns
    Block #233 = 72 txns
    type blockchainInterface interface {
    	// Header returns the current header of the chain (genesis if empty)
    	Header() *types.Header
    
    	// GetReceiptsByHash returns the receipts for a hash
    	GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error)
    
    	// Subscribe subscribes for chain head events
    	SubscribeEvents() blockchain.Subscription
    
    	// GetHeaderByNumber returns the header by number
    	GetHeaderByNumber(block uint64) (*types.Header, bool)
    
    	// GetAvgGasPrice returns the average gas price
    	GetAvgGasPrice() *big.Int
    
    	// AddTx adds a new transaction to the tx pool
    	AddTx(tx *types.Transaction) error
    
    	// State returns a reference to the state
    	State() state.State
    
    	// BeginTxn starts a transition object
    	BeginTxn(parentRoot types.Hash, header *types.Header) (*state.Transition, error)
    
    	// GetBlockByHash gets a block using the provided hash
    	GetBlockByHash(hash types.Hash, full bool) (*types.Block, bool)
    
    	// ApplyTxn applies a transaction object to the blockchain
    	ApplyTxn(header *types.Header, txn *types.Transaction) ([]byte, bool, error)
    
    	stateHelperInterface
    }
    jsonrpc/eth_endpoint.go
    type Filter struct {
    	id string
    
    	// block filter
    	block *headElem
    
    	// log cache
    	logs []*Log
    
    	// log filter
    	logFilter *LogFilter
    
    	// index of the filter in the timer array
    	index int
    
    	// next time to timeout
    	timestamp time.Time
    
    	// websocket connection
    	ws wsConn
    }
    
    
    type FilterManager struct {
    	logger hclog.Logger
    
    	store   blockchainInterface
    	closeCh chan struct{}
    
    	subscription blockchain.Subscription
    
    	filters map[string]*Filter
    	lock    sync.Mutex
    
    	updateCh chan struct{}
    	timer    timeHeapImpl
    	timeout  time.Duration
    
    	blockStream *blockStream
    }
    
    func (f *FilterManager) Run() {
    
    	// watch for new events in the blockchain
    	watchCh := make(chan *blockchain.Event)
    	go func() {
    		for {
    			evnt := f.subscription.GetEvent()
    			if evnt == nil {
    				return
    			}
    			watchCh <- evnt
    		}
    	}()
    
    	var timeoutCh <-chan time.Time
    	for {
    		// check for the next filter to be removed
    		filter := f.nextTimeoutFilter()
    		if filter == nil {
    			timeoutCh = nil
    		} else {
    			timeoutCh = time.After(filter.timestamp.Sub(time.Now()))
    		}
    
    		select {
    		case evnt := <-watchCh:
    			// new blockchain event
    			if err := f.dispatchEvent(evnt); err != nil {
    				f.logger.Error("failed to dispatch event", "err", err)
    			}
    
    		case <-timeoutCh:
    			// timeout for filter
    			if !f.Uninstall(filter.id) {
    				f.logger.Error("failed to uninstall filter", "id", filter.id)
    			}
    
    		case <-f.updateCh:
    			// there is a new filter, reset the loop to start the timeout timer
    
    		case <-f.closeCh:
    			// stop the filter manager
    			return
    		}
    	}
    }
    Setting up the VM connectivity

    Depending on your choice of cloud provider, you may set up connectivity and rules between the VMs using a firewall, security groups, or access control lists.

    As the only part of the polygon-edge that needs to be exposed to other VMs is the libp2p server, simply allowing all communication between VMs on the default libp2p port 1478 is enough.

    Overview

    Cloud setup

    In this guide, our goal is to establish a working polygon-edge blockchain network working with IBFT consensus protocol. The blockchain network will consist of 4 nodes of whom all 4 are validator nodes, and as such are eligible for both proposing block, and validating blocks that came from other proposers. Each of the 4 nodes will run on their own VM, as the idea of this guide is to give you a fully functional ZChains network while keeping the validator keys private to ensure a trustless network setup.

    To achieve that, we will guide you through 4 easy steps:

    1. Take a look at the list of Requirements above

    2. Generate the private keys for each of the validators, and initialize the data directory

    3. Prepare the connection string for the bootnode to be put into the shared genesis.json

    4. Create the genesis.json on your local machine, and send/transfer it to each of the nodes

    5. Start all the nodes

    :::info Number of validators

    There is no minimum to the number of nodes in a cluster, which means clusters with only 1 validator node are possible. Keep in mind that with a single node cluster, there is no crash tolerance and no BFT guarantee.

    The minimum recommended number of nodes for achieving a BFT guarantee is 4 - since in a 4 node cluster, the failure of 1 node can be tolerated, with the remaining 3 functioning normally.

    :::

    Step 1: Initialize data folders and generate validator keys

    To get up and running with ZChains, you need to initialize the data folders, on each node:

    Each of these commands will print the node ID. You will need that information for the next step.

    :::warning Keep your data directory to yourself!

    The data directories generated above, besides initializing the directories for holding the blockchain state, will also generate your validator's private keys. This key should be kept as a secret, as stealing it would render somebody capable of impersonating you as the validator in the network! :::

    Step 2: Prepare the multiaddr connection string for the bootnode

    For a node to successfully establish connectivity, it must know which bootnode server to connect to gain information about all the remaining nodes on the network. The bootnode is sometimes also known as the rendezvous server in p2p jargon.

    bootnode is not a special instance of a Polygon Edge node. Every Polygon Edge node can serve as a bootnode and every Polygon Edge node needs to have a set of bootnodes specified which will be contacted to provide information on how to connect with all remaining nodes in the network.

    To create the connection string for specifying the bootnode, we will need to conform to the multiaddr format:

    In this guide, we will treat the first and second nodes as the bootnodes for all other nodes. What will happen in this scenario is that nodes that connect to the node 1 or node 2 will get information on how to connect to one another through the mutually contacted bootnode.

    :::info You need to specify at least one bootnode to start a node

    At least one bootnode is required, so other nodes in the network can discover each other. More bootnodes are recommended, as they provide resilience to the network in case of outages. In this guide we will list two nodes, but this can be changed on the fly, with no impact on the validity of the genesis.json file. :::

    As the first part of the multiaddr connection string is the <ip_address>, here you will need to enter the IP address as reachable by other nodes, depending on your setup this might be a private or a public IP address, not 127.0.0.1.

    For the <port> we will use 1478, since it is the default libp2p port.

    And lastly, we need the <node_id> which we can get from the output of the previously ran command polygon-edge secrets init --data-dir data-dir command (which was used to generate keys and data directories for the node 1)

    After the assembly, the multiaddr connection string to the node 1 which we will use as the bootnode will look something like this (only the <node_id> which is at the end should be different):

    Similarly, we construct multiaddr for the second bootnode as shown below

    Step 3: Generate the genesis file with the 4 nodes as validators

    This step can be run on your local machine, but you will need the public validator keys for each of the 4 validators.

    Validators can safely share the Public key (address) as displayed below in the output to their secrets init commands, so that you may securely generate the genesis.json with those validators in the initial validator set, identified by their public keys:

    Given that you have received all 4 of the validators' public keys, you can run the following command to generate the genesis.json

    What this command does:

    • The --ibft-validator sets the public key of the validator that should be included in the initial validator set in the genesis block. There can be many initial validators.

    • The --bootnode sets the address of the bootnode that will enable the nodes to find each other. We will use the multiaddr string of the node 1, as mentioned in step 2, although you can add as many bootnodes as you want, as displayed above.

    :::info Premining account balances

    You will probably want to set up your blockchain network with some addresses having "premined" balances.

    To achieve this, pass as many --premine flags as you want per address that you want to be initialized with a certain balance on the blockchain.

    For example, if we would like to premine 1000 ETH to address 0x3956E90e632AEbBF34DEB49b71c28A83Bc029862 in our genesis block, then we would need to supply the following argument:

    Note that the premined amount is in WEI, not ETH.

    :::

    :::info Set the block gas limit

    The default gas limit for each block is 5242880. This value is written in the genesis file, but you may want to increase / decrease it.

    To do so, you can use the flag --block-gas-limit followed by the desired value as shown below :

    :::

    :::info Set system file descriptor limit

    The default file descriptor limit ( maximum number of open files ) on some operating systems is pretty small. If the nodes are expected to have high throughput, you might consider increasing this limit on the OS level.

    For Ubuntu distro the procedure is as follows ( if you're not using Ubuntu/Debian distro, check the official docs for your OS ) :

    • Check current os limits ( open files )

    • Increase open files limit

      • Localy - affects only current session:

      • Globaly or per user ( add limits at the end of /etc/security/limits.conf file ) :

      Optionaly, modify additional parameters, save the file and restart the system. After restart check file descriptor limit again. It should be set to the value you defined in limits.conf file. :::

    After specifying the:

    1. Public keys of the validators to be included in the genesis block as the validator set

    2. Bootnode multiaddr connection strings

    3. Premined accounts and balances to be included in the genesis block

    and generating the genesis.json, you should copy it over to all of the VMs in the network. Depending on your setup you may copy/paste it, send it to the node operator, or simply SCP/FTP it over.

    The structure of the genesis file is covered in the CLI Commands section.

    Step 4: Run all the clients

    :::note Networking on Cloud providers

    Most cloud providers don't expose the IP addresses (especially public ones) as a direct network interface on your VM but rather setup an invisible NAT proxy.

    To allow the nodes to connect to each other in this case you would need to listen on the 0.0.0.0 IP address to bind on all interfaces, but you would still need to specify the IP address or DNS address which other nodes can use to connect to your instance. This is achieved either by using the --nat or --dns argument where you can specify your external IP or DNS address respectively.

    Example

    The associated IP address that you wish to listen on is 192.0.2.1, but it is not directly bound to any of your network interfaces.

    To allow the nodes to connect you would pass the following parameters:

    polygon-edge ... --libp2p 0.0.0.0:10001 --nat 192.0.2.1

    Or, if you wish to specify a DNS address dns/example.io, pass the following parameters:

    polygon-edge ... --libp2p 0.0.0.0:10001 --dns dns/example.io

    This would make your node listen on all interfaces, but also make it aware that the clients are connecting to it through the specified --nat or --dns address.

    :::

    To run the first client:

    To run the second client:

    To run the third client:

    To run the fourth client:

    After running the previous commands, you have set up a 4 node Polygon Edge network, capable of sealing blocks and recovering from node failure.

    :::info Start the client using config file

    Instead of specifying all configuration parameters as CLI arguments, the Client can also be started using a config file by executing the following command:

    Example :

    Currently, we only support json based configuration file, sample config file can be found here

    :::

    :::info Steps to run a non-validator node

    A Non-validator will always sync the latest blocks received from the validator node, you can start a non-validator node by running the following command.

    For example, you can add fifth Non-validator client by executing the following command :

    :::

    :::info Specify the price limit A Zchains e node can be started with a set price limit for incoming transactions.

    The unit for the price limit is wei.

    Setting a price limit means that any transaction processed by the current node will need to have a gas price higher than the set price limit, otherwise it will not be included into a block.

    Having the majority of nodes respect a certain price limit enforces the rule that transactions in the network cannot be below a certain price threshold.

    The default value for the price limit is 0, meaning it is not enforced at all by default.

    Example of using the --price-limit flag:

    It is worth noting that price limits are enforced only on non-local transactions, meaning that the price limit does not apply to transactions added locally on the node. :::

    Local Setup
    Installation

    Other modules

    Crypto

    The Crypto module contains crypto utility functions.

    Chain

    The Chain module contains chain parameters (active forks, consensus engine, etc.)

    • chains - Predefined chain configurations (mainnet, goerli, ibft)

    Helper

    The Helper module contains helper packages.

    • dao - Dao utils

    • enode - Enode encoding/decoding function

    • hex - Hex encoding/decoding functions

    • ipc - IPC connection functions

    Command

    The Command module contains interfaces for CLI commands.

    CLI Command

    This section details the present commands, command flags in the EVMBuilder Edge, and how they're used.

    JSON OUTPUT SUPPORT

    The --json flag is supported on some commands. This flag instructs the command to print the output in JSON format

    Startup Commands

    Command

    server flags

    seal

    SyntaxExample

    server [--seal SHOULD_SEAL]

    Sets the flag indicating that the client should seal blocks. Default: true.


    data-dir

    SyntaxExample

    server [--data-dir DATA_DIRECTORY]

    Used to specify the data directory used for storing EVMBuilder Edge client data. Default: ./test-chain.


    jsonrpc

    SyntaxExample

    server [--jsonrpc JSONRPC_ADDRESS]

    Sets the address and port for the JSON-RPC service address:port. If only port is defined :10001 it will bind to all interfaces 0.0.0.0:10001. If omitted the service will bind to the default address:port. Default address: 0.0.0.0:8545.


    grpc

    SyntaxExample

    server [--grpc-address GRPC_ADDRESS]

    Sets the address and port for the gRPC service address:port. Default address: 127.0.0.1:9632.


    libp2p

    SyntaxExample

    server [--libp2p LIBP2P_ADDRESS]

    Sets the address and port for the libp2p service address:port. Default address: 127.0.0.1:1478.


    prometheus

    SyntaxExample

    server [--prometheus PROMETHEUS_ADDRESS]

    Sets the address and port for the prometheus server address:port. If only port is defined :5001 the service will bind to all interfaces 0.0.0.0:5001. If omitted the service will not be started.


    block-gas-target

    SyntaxExample

    server [--block-gas-target BLOCK_GAS_TARGET]

    Sets the target block gas limit for the chain. Default (not enforced): 0.

    A more detailed explanation on the block gas target can be found in the TxPool section.


    max-peers

    SyntaxExample

    server [--max-peers PEER_COUNT]

    Sets the client's maximum peer count. Default: 40.

    Peer limit should be specified either by using max-peers or max-inbound/outbound-peers flag.


    max-inbound-peers

    SyntaxExample

    server [--max-inbound-peers PEER_COUNT]

    Sets the client's maximum inbound peer count. If max-peers is set, max-inbound-peer limit is calculated using the following formulae.

    max-inbound-peer = InboundRatio * max-peers, where InboundRatio is 0.8.


    max-outbound-peers

    SyntaxExample

    server [--max-outbound-peers PEER_COUNT]

    Sets the client's maximum outbound peer count. If max-peers is set, max-outbound-peer count is calculated using the following formulae.

    max-outbound-peer = OutboundRatio * max-peers, where OutboundRatio is 0.2.


    log-level

    SyntaxExample

    server [--log-level LOG_LEVEL]

    Sets the log level for console output. Default: INFO.


    log-to

    SyntaxExample

    server [--log-to LOG_FILE]

    Defines log file name that will hold all log output from the server command. By default, all server logs will be outputted to console (stdout), but if the flag is set, there will be no output to the console when running server command.


    chain

    SyntaxExample

    server [--chain GENESIS_FILE]

    Specifies the genesis file used for starting the chain. Default: ./genesis.json.


    join

    SyntaxExample

    server [--join JOIN_ADDRESS]

    Specifies the address of the peer that should be joined.


    nat

    SyntaxExample

    server [--nat NAT_ADDRESS]

    Sets the external IP address without the port, as it can be seen by peers.


    dns

    SyntaxExample

    server [--dns DNS_ADDRESS]

    Sets the host DNS address. This can be used to advertise an external DNS. Supports dns,dns4,dns6.


    price-limit

    SyntaxExample

    server [--price-limit PRICE_LIMIT]

    Sets minimum gas price limit to enforce for acceptance into the pool. Default: 1.


    max-slots

    SyntaxExample

    server [--max-slots MAX_SLOTS]

    Sets maximum slots in the pool. Default: 4096.


    config

    SyntaxExample

    server [--config CLI_CONFIG_PATH]

    Specifies the path to the CLI config. Supports .json.


    secrets-config

    SyntaxSecond Tab

    server [--secrets-config SECRETS_CONFIG]

    Sets the path to the SecretsManager config file. Used for Hashicorp Vault, AWS SSM and GCP Secrets Manager. If omitted, the local FS secrets manager is used.


    dev

    SyntaxExample

    server [--dev DEV_MODE]

    Sets the client to dev mode. Default: false.


    dev-interval

    SyntaxExample

    server [--dev-interval DEV_INTERVAL]

    Sets the client's dev notification interval in seconds. Default: 0.


    no-discover

    SyntaxExample

    server [--no-discover NO_DISCOVER]

    Prevents the client from discovering other peers. Default: false.


    restore

    SyntaxExample

    server [--restore RESTORE]

    Restore blocks from the specified archive file


    block-time

    SyntaxExample

    server [--block-time BLOCK_TIME]

    Sets block production time in seconds. Default: 2


    ibft-base-timeout

    SyntaxExample

    server [--ibft-base-timeout IBFT_BASE_TIMEOUT]

    Sets the base value of timeout on IBFT consensus. IBFT consensus timeout is calculated by BaseTimeout + 2^(round), or BaseTimeout * 30 where round exceeds 8. It needs to be larger than block time and BlockTime * 5 is set if it's not specified.


    access-control-allow-origins

    SyntaxExample

    server [--access-control-allow-origins ACCESS_CONTROL_ALLOW_ORIGINS]

    Sets the authorized domains to be able to share responses from JSON-RPC requests. Add multiple flags --access-control-allow-origins "https://example1.com" --access-control-allow-origins "https://example2.com" to authorize multiple domains. If omitted Access-Control-Allow-Origins header will be set to * and all domains will be authorized.


    genesis flags

    dir

    SyntaxExample

    genesis [--dir DIRECTORY]

    Sets the directory for the EVMBuilder Edge genesis data. Default: ./genesis.json.


    name

    SyntaxExample

    genesis [--name NAME]

    Sets the name for the chain. Default: polyton-edge.


    pos

    SyntaxExample

    genesis [--pos IS_POS]

    Sets the flag indicating that the client should use Proof of Stake IBFT. Defaults to Proof of Authority if flag is not provided or false.


    epoch-size

    SyntaxExample

    genesis [--epoch-size EPOCH_SIZE]

    Sets the epoch size for the chain. Default 100000.


    premine

    SyntaxExample

    genesis [--premine ADDRESS:VALUE]

    Sets the premined accounts and balances in the format address:amount. The amount can be in either decimal or hex. Default premined balance: 0x3635C9ADC5DEA00000.


    chainid

    SyntaxExample

    genesis [--chain-id CHAIN_ID]

    Sets the ID of the chain. Default: 100.


    ibft-validators-prefix-path

    SyntaxExample

    genesis [--ibft-validators-prefix-path IBFT_VALIDATORS_PREFIX_PATH]

    Prefix path for validator folder directory. Needs to be present if the flag ibft-validator is omitted.


    ibft-validator

    SyntaxExample

    genesis [--ibft-validator IBFT_VALIDATOR_LIST]

    Sets passed in addresses as IBFT validators. Needs to be present if the flag ibft-validators-prefix-path is omitted.


    block-gas-limit

    SyntaxExample

    genesis [--block-gas-limit BLOCK_GAS_LIMIT]

    Refers to the maximum amount of gas used by all operations in a block. Default: 5242880.


    consensus

    SyntaxExample

    genesis [--consensus CONSENSUS_PROTOCOL]

    Sets consensus protocol. Default: pow.


    bootnode

    SyntaxExample

    genesis [--bootnode BOOTNODE_URL]

    Multiaddr URL for p2p discovery bootstrap. This flag can be used multiple times. Instead of an IP address, the DNS address of the bootnode can be provided.


    max-validator-count

    SyntaxExample

    genesis [--max-validator-count MAX_VALIDATOR_COUNT]

    The maximum number of stakers able to join the validator set in a PoS consensus. This number cannot exceed the value of MAX_SAFE_INTEGER (2^53 - 2).


    min-validator-count

    SyntaxExample

    genesis [--min-validator-count MIN_VALIDATOR_COUNT]

    The minimum number of stakers needed to join the validator set in a PoS consensus. This number cannot exceed the value of max-validator-count. Defaults to 1.


    Operator Commands

    Peer Commands

    peers add flags

    addr

    SyntaxExample

    peers add --addr PEER_ADDRESS

    Peer's libp2p address in the multiaddr format.


    grpc-address

    SyntaxExample

    peers add [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    peers list flags

    grpc-address

    SyntaxExample

    peers list [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    peers status flags

    peer-id

    SyntaxExample

    peers status --peer-id PEER_ID

    Libp2p node ID of a specific peer within p2p network.


    grpc-address

    SyntaxExample

    peers status [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    IBFT Commands

    ibft snapshot flags

    number

    SyntaxExample

    ibft snapshot [--number BLOCK_NUMBER]

    The block height (number) for the snapshot.


    grpc-address

    SyntaxExample

    ibft snapshot [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    ibft candidates flags

    grpc-address

    SyntaxExample

    ibft candidates [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    ibft propose flags

    vote

    SyntaxExample

    ibft propose --vote VOTE

    Proposes a change to the validator set. Possible values: [auth, drop].


    addr

    SyntaxExample

    ibft propose --addr ETH_ADDRESS

    Address of the account to be voted for.


    grpc-address

    SyntaxExample

    peers add [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    ibft status flags

    grpc-address

    SyntaxExample

    peers list [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    ibft switch flags

    chain

    SyntaxExample

    ibft switch [--chain GENESIS_FILE]

    Specifies the genesis file to update. Default: test.


    type

    SyntaxExample

    ibft switch [--type TYPE]

    Specifies the IBFT type to switch. Possible values: [PoS].


    deployment

    SyntaxExample

    ibft switch [--deployment DEPLOYMENT]

    Specifies the height of contract deployment. Only available with PoS.


    from

    SyntaxExample

    ibft switch [--from FROM]


    max-validator-count

    SyntaxExample

    ibft switch [--max-validator-count MAX_VALIDATOR_COUNT]

    The maximum number of stakers able to join the validator set in a PoS consensus. This number cannot exceed the value of MAX_SAFE_INTEGER (2^53 - 2).


    min-validator-count

    SyntaxExample

    ibft switch [--min-validator-count MIN_VALIDATOR_COUNT]

    The minimum number of stakers needed to join the validator set in a PoS consensus. This number cannot exceed the value of max-validator-count. Defaults to 1.

    Specifies the beginning height of the fork.

    Transaction Pool Commands

    txpool status flags

    grpc-address

    SyntaxExample

    txpool status [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    txpool subscribe flags

    grpc-address

    SyntaxExample

    txpool subscribe [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.


    promoted

    SyntaxExample

    txpool subscribe [--promoted LISTEN_PROMOTED]

    Subscribes for promoted tx events in the TxPool.


    dropped

    SyntaxExample

    txpool subscribe [--dropped LISTEN_DROPPED]

    Subscribes for dropped tx events in the TxPool.


    demoted

    SyntaxExample

    txpool subscribe [--demoted LISTEN_DEMOTED]

    Subscribes for demoted tx events in the TxPool.


    added

    SyntaxExample

    txpool subscribe [--added LISTEN_ADDED]

    Subscribes for added tx events to the TxPool.


    enqueued

    SyntaxExample

    txpool subscribe [--enqueued LISTEN_ENQUEUED]

    Subscribes for enqueued tx events in the account queues.


    Blockchain commands

    status flags

    grpc-address

    SyntaxExample

    status [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.

    monitor flags

    grpc-address

    SyntaxExample

    monitor [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.


    Secrets Commands

    secrets init flags

    config

    SyntaxExample

    secrets init [--config SECRETS_CONFIG]

    Sets the path to the SecretsManager config file. Used for Hashicorp Vault. If omitted, the local FS secrets manager is used.


    data-dir

    SyntaxExample

    secrets init [--data-dir DATA_DIRECTORY]

    Sets the directory for the EVMBuilder Edge data if the local FS is used.

    secrets generate flags

    dir

    SyntaxExample

    secrets generate [--dir DATA_DIRECTORY]

    Sets the directory for the secrets manager configuration file Default: ./secretsManagerConfig.json


    type

    SyntaxExample

    secrets generate [--type TYPE]

    Specifies the type of the secrets manager [hashicorp-vault]. Default: hashicorp-vault


    token

    SyntaxExample

    secrets generate [--token TOKEN]

    Specifies the access token for the service


    server-url

    SyntaxExample

    secrets generate [--server-url SERVER_URL]

    Specifies the server URL for the service


    name

    SyntaxExample

    secrets generate [--name NODE_NAME]

    Specifies the name of the node for on-service record keeping. Default: z-edge-node


    namespace

    SyntaxExample

    secrets generate [--namespace NAMESPACE]

    Specifies the namespace used for the Hashicorp Vault secrets manager. Default: admin


    Responses

    Status Response

    The response object is defined using Protocol Buffers.

    minimal/proto/system.proto

    Copy

    Monitor Response

    minimal/proto/system.proto

    Copy

    Utilities

    loadbot flags

    tps

    SyntaxExample

    loadbot [--tps NUMBER_OF_TXNS_PER_SECOND]

    The number of transactions per second to send. Default: 100.


    mode

    SyntaxExample

    loadbot [--mode MODE]

    Sets the loadbot run mode [transfer, deploy]. Default: transfer.


    chain-id

    SyntaxExample

    loadbot [--chain-id CHAIN_ID]

    Sets the network chain ID for transactions. Default: 100.


    gas-price

    SyntaxExample

    loadbot [--gas-price GAS_PRICE]

    The gas price that should be used for the transactions. If omitted, the average gas price is fetched from the network.


    gas-limit

    SyntaxExample

    loadbot [--gas-limit GAS_LIMIT]

    The gas limit that should be used for the transactions. If omitted, the gas limit is estimated before starting the loadbot.


    grpc-address

    SyntaxExample

    loadbot --grpc-address GRPC_ADDRESS

    The GRPC endpoint used to send transactions


    detailed

    SyntaxExample

    loadbot [--detailed DETAILED]

    Flag indicating if the error logs should be shown. Default: false.


    contract

    SyntaxExample

    loadbot [--contract CONTRACT_PATH]

    The path to the contract JSON artifact containing the bytecode. If omitted, a default contract is used.


    sender

    SyntaxExample

    loadbot [--sender ADDRESS]

    Address of the sender account.


    receiver

    SyntaxExample

    loadbot [--receiver ADDRESS]

    Address of the receiver account.


    jsonrpc

    SyntaxExample

    loadbot [--jsonrpc ENDPOINT]

    A JSON RPC endpoint used to send transactions.


    count

    SyntaxExample

    loadbot [--count COUNT]

    The total number of transactions to send. Default: 1000.


    value

    SyntaxExample

    loadbot [--value VALUE]

    The value to send in each transaction.


    max-conns

    SyntaxExample

    loadbot [--max-conns MAX_CONNECTIONS_COUNT]

    Sets the maximum no.of connections allowed per host. Default: 2*tps.


    backup flags

    grpc-address

    SyntaxExample

    backup [--grpc-address GRPC_ADDRESS]

    Address of the gRPC API. Default: 127.0.0.1:9632.


    out

    SyntaxExample

    backup [--out OUT]

    Path of archive file to save.


    from

    SyntaxExample

    from [--from FROM]

    The beginning height of blocks in archive. Default: 0.


    to

    SyntaxExample

    to [--to TO]

    The end height of blocks in archive.


    Genesis Template

    The genesis file should be used to set the initial state of the blockchain (ex. if some accounts should have a starting balance).

    The following ./genesis.json file is generated:

    Copy

    Data Directory

    When executing the data-dir flag, a test-chain folder is generated. The folder structure consists of the following sub-folders:

    • blockchain - Stores the LevelDB for blockchain objects

    • trie - Stores the LevelDB for the Merkle tries

    • keystore - Stores private keys for the client. This includes the libp2p private key and the sealing/validator private key

    • consensus - Stores any consensus information that the client might need while working

    Resources

    Architecture Overview

    We started with the idea of making software that is modular.

    This is something that is present in almost all parts of the ZChains. Below, you will find a brief overview of the built architecture and its layering.

    ZChains Layering

    Polygon Edge Architecture

    Libp2p

    It all starts at the base networking layer, which utilizes libp2p. We decided to go with this technology because it fits into the designing philosophies of ZChains. Libp2p is:

    • Modular

    • Extensible

    • Fast

    Most importantly, it provides a great foundation for more advanced features, which we'll cover later on.

    Synchronization & Consensus

    The separation of the synchronization and consensus protocols allows for modularity and implementation of custom sync and consensus mechanisms - depending on how the client is being run.

    ZChains is designed to offer off-the-shelf pluggable consensus algorithms.

    The current list of supported consensus algorithms:

    • IBFT PoS

    Blockchain

    The Blockchain layer is the central layer that coordinates everything in the ZChains system. It is covered in depth in the corresponding Modules section.

    State

    The State inner layer contains state transition logic. It deals with how the state changes when a new block is included. It is covered in depth in the corresponding Modules section.

    JSON RPC

    The JSON RPC layer is an API layer that dApp developers use to interact with the blockchain. It is covered in depth in the corresponding Modules section.

    TxPool

    The TxPool layer represents the transaction pool, and it is closely linked with other modules in the system, as transactions can be added from multiple entry points.

    GRPC

    The GRPC layer is vital for operator interactions. Through it, node operators can easily interact with the client, providing an enjoyable UX.

    Protocol

    Overview

    The Protocol module contains the logic for the synchronization protocol.

    The Zchains uses libp2p as the networking layer, and on top of that runs gRPC.

    GRPC for Other Nodes

    Status Object

    Server configuration file

    Starting the server with various configuration options can be done using a configuration file instead of using just flags. The command used to start the server with a config file: polygon-edge server --config <config_file_name>

    Export config file with default configuration

    The configuration with default settings for the ZChains server can be exported into a config file in either yaml or json file format. This file can be used as a template for running the server using a configuration file.

    YAML

    To generate the config file in yaml format:

    or just

    the config file named default-config.yaml will be created in the same directory that this command has been run from.

    File example:

    JSON

    To generate the config file in json format:

    the config file named default-config.json will be created in the same directory that this command has been run from.

    File example:

    Checkout section to get information on how to use these parameters.

    Typescript schema

    The following is the sample format for the configuration file. It's written in TypeScript to express the properties types (string, number, boolean), from it you could derive your configuration. It's worth mentioning that the PartialDeep type from type-fest is used to express that all properties are optional.

    State in Ethereum

    Merkle Trees

    Before discussing the main data objects in Ethereum, we need to go over what Merkle trees are, and what are the properties that make them useful.

    A Merkle tree is a tree data structure, where the leaf nodes contain the hash of a block of data and the non-leaf nodes contain the hash of its children nodes.

    Example Merkle tree

    In a Merkle tree, any change to the underlying data causes the hash of the node referring to the data to change. Since each parent node hash depends on the data of its children, any change to the data of a child node causes the parent hash to change. This happens to each parent node up to the root node. Therefore, any change to the data at the leaf nodes causes the root node hash to change. From this, we can derive two important properties:

    1. We don't need to compare all the data across the leaf nodes to know if they have the same data. We can just compare the root node hash.

    2. If we want to prove that specific data is part of the tree, we can use a technique called Merkle proofs. We won't dive into details here but it is an easy and effective way to prove that a piece of data is in the Merkle tree.

    The first property is important because it makes it possible to store only a hash of the root node to represent the data at that point in time. This means we only need to store the root hash of the tree representing the block on the blockchain (as opposed to storing all the data in the blockchain) and still keep the data immutable.

    World State

    The world state is a mapping between addresses (accounts) and account states.

    The world state is not stored on the blockchain, but the Yellow Paper states it is expected that implementations store this data in a trie (also referred to as the state database or state trie). The world state can be seen as the global state that is constantly updated by transaction executions.

    All the information about Ethereum accounts lives in the world state and is stored in the world state trie. If you want to know the balance of an account, or the current state of a smart contract, you query the world state trie to retrieve the account state of that account. We’ll describe how this data is stored shortly.

    Account State

    In Ethereum, there are two types of accounts: External Owned Accounts (EOA) and Contract Accounts.

    An EOA account is an account that regular users have, that they can use to send Ether to one another and deploy smart contracts with.

    A contract account is an account that is created when a smart contract is deployed. Every smart contract has its own Ethereum account.

    The account state contains information about an Ethereum account. For example, it stores how much Ether an account has, and the number of transactions sent by the account. Each account has an account state.

    Let's take a look into each one of the fields in the account state:

    • nonce - Number of transactions sent from this address (if this is an External Owned Account - EOA) or the number of contract creations made by this account

    • balance - Total Ether (in Wei) owned by this account

    • storageRoot - Hash of the root node of the account storage trie

    • codeHash - For contract accounts, the hash of the EVM code of this account. For EOAs, this will be empty.

    One important detail about the account state is that all fields (except the codeHash) are mutable. For example, when one account sends some Ether to another, the nonce will be incremented, and the balance will be updated to reflect the new balance.

    One of the consequences of the codeHash being immutable is that if you deploy a contract with a bug, you can't update the same contract. You need to deploy a new contract (the buggy version will be available forever). This is why it is important to use tools like Truffle to develop and test your smart contracts and follow the best practices when working with Solidity.

    The Account Storage trie is where the data associated with an account is stored. This is only relevant for Contract Accounts, as for EOAs the storageRoot is empty, and the codeHash is the hash of an empty string.

    Transactions

    Transactions are what make the state change from the current state to the next state. In Ethereum, we have three types of transactions:

    1. Transactions that transfer value between two EOAs (e.g, change the sender and receiver account balances)

    2. Transactions that send a message call to a contract (e.g, set a value in the smart contract by sending a message call that executes a setter method)

    3. Transactions that deploy a contract (therefore, create an account, the contract account)

    :::tip Clarification Technically, 1 and 2 are the same - transactions that send message calls that affect an account state, either EOA or contract accounts.

    It is easier to think about them as three different types. :::

    These are the fields of a transaction:

    • nonce - Number of transactions sent by the account that created the transaction

    • gasPrice - Value (in Wei) that will be paid per unit of gas for the computation costs of executing this transaction

    • gasLimit - Maximum amount of gas to be used while executing this transaction

    • to

    Not surprisingly, all transactions in a block are stored in a trie. The root hash of this trie is stored in the... block header! Let's take a look into the anatomy of an Ethereum block.

    Blocks

    The block header is divided into two parts, the block header and the block body.

    The block header is the blockchain part of Ethereum. This is the structure that contains the hash of its predecessor block (also known as parent block), building a cryptographically guaranteed chain.

    The block body contains a list of transactions that have been included in this block, and a list of uncle (ommer) block headers.

    The block header contains the following fields:

    • parentHash - Hash of the block header from the previous block. Each block contains a hash of the previous block, all the way to the first block in the chain. This is how all the data is protected against modifications (any modification in a previous block would change the hash of all blocks after the modified block)

    • ommersHash - Hash of the uncle blocks headers part of the block body

    • beneficiary - Ethereum account that will get fees for mining this block

    Recap

    Ethereum has 4 types of tries:

    1. The world state trie contains the mapping between addresses and account states. The hash of the root node of the world state trie is included in a block (in the stateRoot field) to represent the current state when that block was created. We only have one world state trie

    2. The account storage trie contains the data associated with a smart contract. The hash of the root node of the Account storage trie is included in the account state (in the storageRoot field). We have one Account storage trie for each account

    3. The transaction trie contains all the transactions included in a block. The hash of the root node of the Transaction trie is included in the block header (in the transactionsRoot field). We have one transaction trie per block

    Objects covered:

    1. World state: the hard drive of the distributed computer that is Ethereum. It is a mapping between addresses and account states

    2. Account state: stores the state of each one of Ethereum's accounts. It also contains the storageRoot of the account state trie, which contains the storage data for the account

    3. Transaction: represents a state transition in the system. It can be a funds transfer, a message call, or a contract deployment

    4. Block

    Credits

    This clear and concise walkthrough of Ethereum's yellow paper was originally posted by Lucas Saldanha, on .

    📜 Resources

    TxPool

    Overview

    The TxPool module represents the transaction pool implementation, where transactions are added from different parts of the system. The module also exposes several useful features for node operators, which are covered below.

    Operator Commands

    Node operators can query these GRPC endpoints, as described in the section.

    Processing Transactions

    The addImpl method is the bread and butter of the TxPool module. It is the central place where transactions are added in the system, being called from the GRPC service, JSON RPC endpoints, and whenever the client receives a transaction through the gossip protocol.

    It takes in as an argument ctx, which just denotes the context from which the transactions are being added (GRPC, JSON RPC...). The other parameter is the list of transactions to be added to the pool.

    The key thing to note here is the check for the From field within the transaction:

    • If the From field is empty, it is regarded as an unencrypted/unsigned transaction. These kinds of transactions are only accepted in development mode

    • If the From field is not empty, that means that it's a signed transaction, so signature verification takes place

    After all these validations, the transactions are considered to be valid.

    Data structures

    The fields in the TxPool object that can cause confusion are the queue and sorted lists.

    • queue - Heap implementation of a sorted list of account transactions (by nonce)

    • sorted - Sorted list for all the current promoted transactions (all executable transactions). Sorted by gas price

    Gas limit error management

    Whenever you submit a transaction, there are three ways it can be processed by the TxPool.

    1. All pending transactions can fit in a block

    2. One or more pending transactions can not fit in a block

    3. One or more pending transactions will never fit in a block

    Here, the word fit means that the transaction has a gas limit that is lower than the remaining gas in the TxPool.

    The first scenario does not produce any error.

    Second scenario

    • The TxPool remaining gas is set to the gas limit of the last block, lets say 5000

    • A first transaction is processed and consumes 3000 gas of the TxPool

      • The remaining gas of the TxPool is now 2000

    • A second transaction, which is the same as the first one - they both consume 3000 units of gas, is submitted

    Third scenario

    • The TxPool remaining gas is set to the gas limit of the last block, lets say 5000

    • A first transaction is processed and consumes 3000 gas of the TxPool

      • The remaining gas of the TxPool is now 2000

    • A second transaction, with a gas field set to

    This happens whenever you get the following error:

    Block Gas Target

    There are situations when nodes want to keep the block gas limit below or at a certain target on a running chain.

    The node operator can set the target gas limit on a specific node, which will try to apply this limit to newly created blocks. If the majority of the other nodes also have a similar (or same) target gas limit set, then the block gas limit will always hover around that block gas target, slowly progressing towards it (at max 1/1024 * parent block gas limit) as new blocks are created.

    Example scenario

    • The node operator sets the block gas limit for a single node to be 5000

    • Other nodes are configured to be 5000 as well, apart from a single node which is configured to be 7000

    • When the nodes who have their gas target set to 5000 become proposers, they will check to see if the gas limit is already at the target

    ulimit -u 65535
    sudo vi /etc/security/limits.conf  # we use vi, but you can use your favorite text editor
    # /etc/security/limits.conf
    #
    #Each line describes a limit for a user in the form:
    #
    #<domain>        <type>  <item>  <value>
    #
    #Where:
    #<domain> can be:
    #        - a user name
    #        - a group name, with @group syntax
    #        - the wildcard *, for default entry
    #        - the wildcard %, can be also used with %group syntax,
    #                 for maxlogin limit
    #        - NOTE: group and wildcard limits are not applied to root.
    #          To apply a limit to the root user, <domain> must be
    #          the literal username root.
    #
    #<type> can have the two values:
    #        - "soft" for enforcing the soft limits
    #        - "hard" for enforcing hard limits
    #
    #<item> can be one of the following:
    #        - core - limits the core file size (KB)
    #        - data - max data size (KB)
    #        - fsize - maximum filesize (KB)
    #        - memlock - max locked-in-memory address space (KB)
    #        - nofile - max number of open file descriptors
    #        - rss - max resident set size (KB)
    #        - stack - max stack size (KB)
    #        - cpu - max CPU time (MIN)
    #        - nproc - max number of processes
    #        - as - address space limit (KB)
    #        - maxlogins - max number of logins for this user
    
    #        - maxsyslogins - max number of logins on the system
    #        - priority - the priority to run user process with
    #        - locks - max number of file locks the user can hold
    #        - sigpending - max number of pending signals
    #        - msgqueue - max memory used by POSIX message queues (bytes)
    #        - nice - max nice priority allowed to raise to values: [-20, 19]
    #        - rtprio - max realtime priority
    #        - chroot - change root to directory (Debian-specific)
    #
    #<domain>      <type>  <item>         <value>
    #
    
    #*               soft    core            0
    #root            hard    core            100000
    #*               hard    rss             10000
    #@student        hard    nproc           20
    #@faculty        soft    nproc           20
    #@faculty        hard    nproc           50
    #ftp             hard    nproc           0
    #ftp             -       chroot          /ftp
    #@student        -       maxlogins       4
    
    *               soft    nofile          65535
    *               hard    nofile          65535
    
    # End of file
    node-1> polygon-edge secrets init --data-dir data-dir
    node-2> polygon-edge secrets init --data-dir data-dir
    node-3> polygon-edge secrets init --data-dir data-dir
    node-4> polygon-edge secrets init --data-dir data-dir
    /ip4/<ip_address>/tcp/<port>/p2p/<node_id>
    /ip4/<public_or_private_ip>/tcp/1478/p2p/16Uiu2HAmJxxH1tScDX2rLGSU9exnuvZKNM9SoK3v315azp68DLPW
    /ip4/<public_or_private_ip>/tcp/1478/p2p/16Uiu2HAmS9Nq4QAaEiogE4ieJFUYsoH28magT7wSvJPpfUGBj3Hq 
    [SECRETS INIT]
    Public key (address) = 0xC12bB5d97A35c6919aC77C709d55F6aa60436900
    Node ID              = 16Uiu2HAmVZnsqvTwuzC9Jd4iycpdnHdyVZJZTpVC8QuRSKmZdUrf
    polygon-edge genesis --consensus ibft --ibft-validator=0xC12bB5d97A35c6919aC77C709d55F6aa60436900 --ibft-validator=<2nd_validator_pubkey> --ibft-validator=<3rd_validator_pubkey> --ibft-validator=<4th_validator_pubkey> --bootnode=<first_bootnode_multiaddr_connection_string_from_step_2> --bootnode <second_bootnode_multiaddr_connection_string_from_step_2> --bootnode <optionally_more_bootnodes>
    --premine=0x3956E90e632AEbBF34DEB49b71c28A83Bc029862:1000000000000000000000
    const (
    	GenesisFileName       = "./genesis.json"
    	DefaultChainName      = "example"
    	DefaultChainID        = 100
    	DefaultPremineBalance = "0x3635C9ADC5DEA00000"
    	DefaultConsensus      = "pow"
    	GenesisGasUsed        = 458752
    	GenesisGasLimit       = 5242880 // The default block gas limit
    )
    --block-gas-limit 1000000000
    ubuntu@ubuntu:~$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 15391
    max locked memory       (kbytes, -l) 65536
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 15391
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    node-1> polygon-edge server --data-dir ./data-dir --chain genesis.json  --libp2p 0.0.0.0:1478 --nat <public_or_private_ip> --seal
    node-2> polygon-edge server --data-dir ./data-dir --chain genesis.json --libp2p 0.0.0.0:1478 --nat <public_or_private_ip> --seal
    node-3> polygon-edge server --data-dir ./data-dir --chain genesis.json --libp2p 0.0.0.0:1478 --nat <public_or_private_ip> --seal
    node-4> polygon-edge server --data-dir ./data-dir --chain genesis.json --libp2p 0.0.0.0:1478 --nat <public_or_private_ip> --seal
    polygon-edge server --config <config_file_path>
    polygon-edge server --config ./test/config-node1.json
    polygon-edge server --data-dir <directory_path> --chain <genesis_filename>  --libp2p <IPAddress:PortNo> --nat <public_or_private_ip>
    polygon-edge server --data-dir ./data-dir --chain genesis.json --libp2p 0.0.0.0:1478 --nat<public_or_private_ip>
    polygon-edge server --price-limit 100000 ...
    service TxnPoolOperator {
        // Status returns the current status of the pool
        rpc Status(google.protobuf.Empty) returns (TxnPoolStatusResp);
    
        // AddTxn adds a local transaction to the pool
        rpc AddTxn(AddTxnReq) returns (google.protobuf.Empty);
    
        // Subscribe subscribes for new events in the txpool
        rpc Subscribe(google.protobuf.Empty) returns (stream TxPoolEvent);
    }
    

    keccak - Keccak functions

  • rlputil - Rlp encoding/decoding helper function

  • Working with node

    Description

    server

    The default command that starts the blockchain client, by bootstrapping all modules together

    genesis

    Generates a genesis.json file, which is used to set a predefined chain state before starting the client. The structure of the genesis file is described below

    Command

    Description

    peers add

    Adds a new peer using their libp2p address

    peers list

    Lists all the peers the client is connected to through libp2p

    peers status

    Returns the status of a specific peer from the peers list, using the libp2p address

    Command

    Description

    ibft snapshot

    Returns the IBFT snapshot

    ibft candidates

    Queries the current set of proposed candidates, as well as candidates that have not been included yet

    ibft propose

    Proposes a new candidate to be added/removed from the validator set

    ibft status

    Returns the overall status of the IBFT client

    ibft switch

    Add fork configurations into genesis.json file to switch IBFT type

    Command

    Description

    txpool status

    Returns the number of transactions in the pool

    txpool subscribe

    Subscribes for events in the transaction pool

    Command

    Description

    status

    Returns the status of the client. The detailed response can be found below

    monitor

    Subscribes to a blockchain event stream. The detailed response can be found below

    version

    Returns the current version of the client

    Command

    Description

    secrets init

    Initializes the private keys to the corresponding secrets manager

    secrets generate

    Generates a secrets manager configuration file which can be parsed by the EVMBuilder Edge

    Protocol Buffers
    service V1 {
        // Returns status information regarding the specific point in time
        rpc GetCurrent(google.protobuf.Empty) returns (V1Status);
        
        // Returns any type of object (Header, Body, Receipts...)
        rpc GetObjectsByHash(HashRequest) returns (Response);
        
        // Returns a range of headers
        rpc GetHeaders(GetHeadersRequest) returns (Response);
        
        // Watches what new blocks get included
        rpc Watch(google.protobuf.Empty) returns (stream V1Status);
    }
    message V1Status {
        string difficulty = 1;
        string hash = 2;
        int64 number = 3;
    }
    CLI Commands
    message ServerStatus {
        int64 network = 1;
    
        string genesis = 2;
    
        Block current = 3;
    
        string p2pAddr = 4;
    
        message Block {
            int64 number = 1;
            string hash = 2;
        }
    }
    message BlockchainEvent {
        // The "repeated" keyword indicates an array
        repeated Header added = 1;
        repeated Header removed = 2;
    
        message Header {
            int64 number = 1;
            string hash = 2;
        }
    }
    {
        "name": "example",
        "genesis": {
            "nonce": "0x0000000000000000",
            "gasLimit": "0x0000000000001388",
            "difficulty": "0x0000000000000001",
            "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
            "coinbase": "0x0000000000000000000000000000000000000000",
            "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
        },
        "params": {
            "forks": {},
            "chainID": 100,
            "engine": {
                "pow": {}
            }
        },
        "bootnodes": []
    }
    polygon-edge server export --type yaml
    polygon-edge server export
    chain_config: ./genesis.json
    secrets_config: ""
    data_dir: ""
    block_gas_target: "0x0"
    grpc_addr: ""
    jsonrpc_addr: ""
    telemetry:
      prometheus_addr: ""
    network:
      no_discover: false
      libp2p_addr: 127.0.0.1:1478
      nat_addr: ""
      dns_addr: ""
      max_peers: -1
      max_outbound_peers: -1
      max_inbound_peers: -1
    seal: true
    tx_pool:
      price_limit: 0
      max_slots: 4096
    log_level: INFO
    restore_file: ""
    block_time_s: 2
    headers:
      access_control_allow_origins:
        - '*'
    log_to: ""
    polygon-edge server export --type json
    {
      "chain_config": "./genesis.json",
      "secrets_config": "",
      "data_dir": "",
      "block_gas_target": "0x0",
      "grpc_addr": "",
      "jsonrpc_addr": "",
      "telemetry": {
        "prometheus_addr": ""
      },
      "network": {
        "no_discover": false,
        "libp2p_addr": "127.0.0.1:1478",
        "nat_addr": "",
        "dns_addr": "",
        "max_peers": -1,
        "max_outbound_peers": -1,
        "max_inbound_peers": -1
      },
      "seal": true,
      "tx_pool": {
        "price_limit": 0,
        "max_slots": 4096
      },
      "log_level": "INFO",
      "restore_file": "",
      "block_time_s": 2,
      "headers": {
        "access_control_allow_origins": [
          "*"
        ]
      },
      "log_to": ""
    }
    import { PartialDeep } from 'type-fest';
    
    type ServerConfig = PartialDeep<{
      chain_config: string; // <genesis_file_path>
      secrets_config: string; // <secrets_file_path>
      data_dir: string; // <data_directory_path>
      block_gas_target: string; // <block_gas_limit>
      grpc_addr: string; // <grpc_listener_address>
      jsonrpc_addr: string; // <json_rpc_listener_address>
      telemetry: {
        prometheus_addr: string; // <prometheus_listener_address>
      };
      network: {
        no_discover: boolean; // <enable/disable_discovery>,
        libp2p_addr: string; // <libp2p_server_address>,
        nat_addr: string; // <nat_address>,
        dns_addr: string; // <dns_address>,
        max_peers: number; // <maximum_allowded_peers>,
        max_inbound_peers: number; // <maximum_allowded_inbound_peers>,
        max_outbound_peers: number; // <maximum_allowded_outbound_peers>
      };
      seal: boolean; // <enable/disable_block_sealing>
      txpool: {
        price_limit: number; // <minimum_gas_price_limit>
        max_slots: number; // <maximum_txpool_slots>
      };
      log_level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'DPANIC' | 'PANIC' | 'FATAL'; // <log_level>
      restore_file: string; // <restore_file_path>
      block_time_s: number; // <block_time_seconds>
      headers: Record<string, any>;
      log_to: string; // <log_to>
    }>
  • If this transaction is transferring Ether, the address of the EOA account that will receive a value transfer

  • If this transaction is sending a message to a contract (e.g, calling a method in the smart contract), this is address of the contract

  • If this transaction is creating a contract, this value is always empty

  • value

    • If this transaction is transferring Ether, the amount in Wei that will be transferred to the recipient account

    • If this transaction is sending a message to a contract, the amount of Wei payable by the smart contract receiving the message

    • If this transaction is creating a contract, this is the amount of Wei that will be added to the balance of the created contract

  • v, r, s - Values used in the cryptographic signature of the transaction used to determine the sender of the transaction

  • data (only for value transfer and sending a message call to a smart contract) -Input data of the message call ( e.g, imagine you are trying to execute a setter method in your smart contract, the data field would contain the identifier of the setter method, and the value that should be passed as a parameter)

  • init (only for contract creation) - The EVM-code utilized for initialization of the contract

  • stateRoot - Hash of the root node of the world state trie (after all transactions are executed)
  • transactionsRoot - Hash of the root node of the transactions trie. This trie contains all transactions in the block body

  • receiptsRoot - Every time a transaction is executed, Ethereum generates a transaction receipt that contains information about the transaction execution. This field is the hash of the root node of the transactions receipt trie

  • logsBloom - Bloom filter that can be used to find out if logs were generated on transactions in this block (if you want more details check this Stack Overflow answer). This avoids storing logs in the block (saving a lot of space)

  • difficulty - Difficulty level of this block. This is a measure of how hard it was to mine this block (I'm not diving into the details of how this is calculated in this post)

  • number - Number of ancestor blocks. This represents the height of the chain (how many blocks are in the chain). The genesis block has number zero

  • gasLimit - Each transaction consumes gas. The gas limit specifies the maximum gas that can be used by the transactions included in the block. It is a way to limit the number of transactions in a block

  • gasUsed - Sum of the gas cost of each transaction in the block

  • timestamp - Unix timestamp when the block was created. Note that due to the decentralized nature of Ethereum, we can't trust in this value (especially when implementing smart contracts that have time-related business logic)

  • extraData - Arbitrary byte array that can contain anything. When a miner is creating the block, it can choose to add anything in this field

  • mixHash - Hash used to verify that a block has been mined properly (if you want to really understand this, read about the Ethash proof-of-work function)

  • nonce - Same as the mixHash, this value is used to verify that a block has been mined properly

  • The transaction receipt trie contains all the transaction receipts for the transactions included in a block. The hash of the root node of the transaction receipts trie is included in also included in the block header (in the receiptsRoot field); We have one transaction receipts trie per block
    : contains the link to the previous block (parentHash) and contains a group of transactions that, when executed, will yield the new state of the system. It also contains the stateRoot, the transactionRoot and the receiptsRoot, the hash of the root nodes of the world state trie, the transaction trie, and the transaction receipts trie, respectively
    How does Ethereum work anyway?
  • A (Practical) Walkthrough of Smart Contract Storage

  • Inside an Ethereum transaction

  • Life Cycle of an Ethereum Transaction

  • Ethereum Design Rationale

  • his personal blog
    Merkle Trees
    Merkle Proofs
    How is data stored in Ethereum?
    Diving into Ethereum's world state
    World State
    Account State
    Block Structure
    Main Diagram
  • Since the remaining gas of the TxPool is lower than the transaction gas, it cannot be processed in the current block

    • It is put back into a pending transaction queue so that it can be processed in the next block

  • The first block is written, lets call it block #1

  • The TxPool remaining gas is set to the parent block - block #1's gas limit

  • The transaction which was put back into the TxPool pending transaction queue is now processed and written in the block

    • The TxPool remaining gas is now 2000

  • The second block is written

  • ...

  • 6000
    is submitted
  • Since the block gas limit is lower than the transaction gas, this transaction is discarded

    • It will never be able to fit in a block

  • The first block is written

  • ...

  • If the gas limit is not at the target (it is greater / lower), the proposer node will set the block gas target to at most (1/1024 * parent gas limit) in the direction of the target

    1. Ex: parentGasLimit = 4500 and blockGasTarget = 5000, the proposer will calculate the gas limit for the new block as 4504.39453125 (4500/1024 + 4500)

    2. Ex: parentGasLimit = 5500 and blockGasTarget = 5000, the proposer will calculate the gas limit for the new block as 5494.62890625 (5500 - 5500/1024)

  • This ensures that the block gas limit in the chain will be kept at the target, because the single proposer who has the target configured to 7000 cannot advance the limit much, and the majority of the nodes who have it set at 5000 will always attempt to keep it there

  • CLI Commands
    TxPool Error scenario #1
    TxPool Error scenario #2

    Manage private keys

    Overview

    The ZChains has two types of private keys that it directly manages:

    • Private key used for the consensus mechanism

    • Private key used for networking by libp2p

    Currently, the ZChains doesn't offer support for direct account management.

    Based on the directory structure outlined in the , the ZChains stores these mentioned key files in two distinct directories - consensus and keystore.

    Key format

    The private keys are stored in simple Base64 format, so they can be human-readable and portable.

    :::info Key Type All private key files generated and used inside the ZChains are relying on ECDSA with the curve .

    As the curve is non-standard, it cannot be encoded and stored in any standardized PEM format. Importing keys that don't conform to this key type is not supported. :::

    Consensus Private Key

    The private key file mentioned as the consensus private key is also referred to as the validator private key. This private key is used when the node is acting as a validator in the network and needs to sign new data.

    The private key file is located in consensus/validator.key, and adheres to the mentioned.

    Networking Private Key

    The private key file mentioned for networking is used by libp2p to generate the corresponding PeerID, and allow the node to participate in the network.

    It is located in keystore/libp2p.key, and adheres to the mentioned.

    Import / Export

    As the key files are stored in simple Base64 on disk, they can be easily backed up or imported.

    :::caution Changing the key files Any kind of change made to the key files on an already set up / running network can lead to serious network/consensus disruption, since the consensus and peer discovery mechanisms store the data derived from these keys in node-specific storage, and rely on this data to initiate connections and perform consensus logic :::

    Set up GCP Secrets Manager

    Overview

    Currently, the ZChains is concerned with keeping 2 major runtime secrets:

    • The validator private key used by the node, if the node is a validator

    • The networking private key used by libp2p, for participating and communicating with other peers

    For additional information, please read through the

    The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.

    Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager, which of course is an abstraction.

    The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.

    This article details the necessary steps to get the ZChains up and running with .

    :::info previous guides It is highly recommended that before going through this article, articles on and are read. :::

    Prerequisites

    GCP Billing Account

    In order to utilize GCP Secrets Manager, the user has to have enabled on the GCP portal. New Google accounts on GCP platform are provided with some funds to get started, as a king of free trial. More info

    Secrets Manager API

    The user will need to enable the GCP Secrets Manager API, before he can use it. This can be done via . More info:

    GCP Credentials

    Finally, the user needs to generate new credentials that will be used for authentication. This can be done by following the instructions posted . The generated json file containing credentials, should be transferred to each node that needs to utilize GCP Secrets Manager.

    Required information before continuing:

    • Project ID (the project id defined on GCP platform)

    • Credentials File Location (the path to the json file containing the credentials)

    Step 1 - Generate the secrets manager configuration

    In order for the ZChains to be able to seamlessly communicate with the GCP SM, it needs to parse an already generated config file, which contains all the necessary information for secret storage on GCP SM.

    To generate the configuration, run the following command:

    Parameters present:

    • PATH is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json

    • NODE_NAME is the name of the current node for which the GCP SM configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node

    • PROJECT_ID

    :::caution Node names Be careful when specifying node names.

    The ZChains uses the specified node name to keep track of the secrets it generates and uses on the GCP SM. Specifying an existing node name can have consequences of failing to write secret to GCP SM.

    Secrets are stored on the following base path: projects/PROJECT_ID/NODE_NAME :::

    Step 2 - Initialize secret keys using the configuration

    Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    Step 3 - Generate the genesis file

    The genesis file should be generated in a similar manner to the and guides, with minor changes.

    Since GCP SM is being used instead of the local file system, validator addresses should be added through the --ibft-validator flag:

    Step 4 - Start the Polygon Edge client

    Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server command.

    The server command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config flag:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    Query operator information

    Prerequisites

    This guide assumes you have followed the Local Setup or guide on how to set up an IBFT cluster on the cloud.

    A functioning node is required in order to query any kind of operator information.

    With the ZChains, node operators are in control and informed about what the node they're operating is doing. At any time, they can use the node information layer, built on top of gRPC, and get meaningful information - no log sifting required.

    :::note

    If your node isn't running on 127.0.0.1:8545 you should add a flag --grpc-address <address:port> to the commands listed in this document.

    :::

    Peer information

    Peers list

    To get a complete list of connected peers (including the running node itself), run the following command:

    This will return a list of libp2p addresses that are currently peers of the running client.

    Peer status

    For the status of a specific peer, run:

    With the address parameter being the libp2p address of the peer.

    IBFT info

    Lots of times, an operator might want to know about the state of the operating node in IBFT consensus.

    Luckily, the Zchains provides an easy way to find this information.

    Snapshots

    Running the following command returns the most recent snapshot.

    To query the snapshot at a specific height (block number), the operator can run:

    Candidates

    To get the latest info on candidates, the operator can run:

    This command queries the current set of proposed candidates, as well as candidates that have not been included yet

    Status

    The following command returns the current validator key of the running IBFT client:

    Transaction pool

    To find the current number of transactions in the transaction pool, the operator can run:

    Set up Hashicorp Vault

    Overview

    Currently, the ZChains is concerned with keeping 2 major runtime secrets:

    • The validator private key used by the node, if the node is a validator

    • The networking private key used by libp2p, for participating and communicating with other peers

    For additional information, please read through the

    The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.

    Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager, which of course is an abstraction.

    The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.

    This article details the necessary steps to get the ZChains up and running with a server.

    :::info previous guides It is highly recommended that before going through this article, articles on and are read. :::

    Prerequisites

    This article assumes that a functioning instance of the Hashicorp Vault server is already set up.

    Additionally, it is required that the Hashicorp Vault server being used for the ZChains should have enabled KV storage.

    Required information before continuing:

    • The server URL (the API URL of the Hashicorp Vault server)

    • Token (access token used for access to the KV storage engine)

    Step 1 - Generate the secrets manager configuration

    In order for the ZChains to be able to seamlessly communicate with the Vault server, it needs to parse an already generated config file, which contains all the necessary information for secret storage on Vault.

    To generate the configuration, run the following command:

    Parameters present:

    • PATH is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json

    • TOKEN is the access token previously mentioned in the

    • SERVER_URL is the URL of the API for the Vault server, also mentioned in the

    :::caution Node names Be careful when specifying node names.

    The ZChains uses the specified node name to keep track of the secrets it generates and uses on the Vault instance. Specifying an existing node name can have consequences of data being overwritten on the Vault server.

    Secrets are stored on the following base path: secrets/node_name :::

    Step 2 - Initialize secret keys using the configuration

    Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    Step 3 - Generate the genesis file

    The genesis file should be generated in a similar manner to the and guides, with minor changes.

    Since Hashicorp Vault is being used instead of the local file system, validator addresses should be added through the --ibft-validator flag:

    Step 4 - Start the Polygon Edge client

    Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server command.

    The server command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config flag:

    The PATH param is the location of the previously generated secrets manager param from step 1.

    Development Roadmap

    Q2 2024: Development of ZChains Network, Swap and Bridge with EVM

    1. ZChains L1 Network Development:

      • Implementation based on EVM and Proof of Stake (PoS) mechanism.

      • Development of swapping and bridging mechanism for interoperability with EVM-compatible networks.

    2. Token Transfer Mechanism:

      • Seamless transfer of tokens like USDT from EVM networks to ZChains and vice versa.

      • Process includes locking/minting and burning/unlocking of tokens for cross-network transfers.

    Q2-Q3: Development of Launchpad dApp

    1. Launchpad Features:

      • Offering Audit, KYC and SAFU badges for grading purpose.

      • Intuitive interfaces for users to easily mint, buy, and trade tokens, making it accessible to a broader audience.

      • Portal to help you easily update content for your launchpad.

    Q3-Q4: Development of Multichain Wallet

    1. Network Support and Functionality:

      • Compatibility with ZChains and all EVM networks.

      • Features for coin transfer and standard wallet functionalities.

    2. User Interface and Technical Specifications:

    Future Development Phases (Beyond Q3):

    • Continuous Integration of dApps:

      • Continue building various dApps with high TVL and user base, aimed to increase the adoption of ZChains and ZCD, creating limitless possibilities with ZChains.

    • Continuous Network and Feature Expansion:

    This roadmap outlines a progressive development strategy for the ZChains, with a focus on expanding its ecosystem and focused in delivering experience-based services.

    Query JSON RPC endpoints

    Overview

    The JSON-RPC layer of the ZChains provides developers with the functionality of easily interacting with the blockchain, through HTTP requests.

    This example covers using tools like curl to query information, as well as starting the chain with a premined account, and sending a transaction.

    Step 1: Create a genesis file with a premined account

    To generate a genesis file, run the following command:

    The premine flag sets the address that should be included with a starting balance in the genesis file. In this case, the address 0x1010101010101010101010101010101010101010 will have a starting default balance of 0x3635C9ADC5DEA00000 wei.

    If we wanted to specify a balance, we can separate out the balance and address with a :, like so:

    The balance can be either a hex or uint256 value.

    :::warning Only premine accounts for which you have a private key! If you premine accounts and do not have a private key to access them, you premined balance will not be usable :::

    Step 2: Start the ZChains in dev mode

    To start the ZChains in development mode, which is explained in the section, run the following:

    Step 3: Query the account balance

    Now that the client is up and running in dev mode, using the genesis file generated in step 1, we can use a tool like curl to query the account balance:

    The command should return the following output:

    Step 4: Send a transfer transaction

    Now that we've confirmed the account we set up as premined has the correct balance, we can transfer some ether:

    // AddTx adds a new transaction to the pool
    func (t *TxPool) AddTx(tx *types.Transaction) error {
    	if err := t.addImpl("addTxn", tx); err != nil {
    		return err
    	}
    
    	// broadcast the transaction only if network is enabled
    	// and we are not in dev mode
    	if t.topic != nil && !t.dev {
    		txn := &proto.Txn{
    			Raw: &any.Any{
    				Value: tx.MarshalRLP(),
    			},
    		}
    		if err := t.topic.Publish(txn); err != nil {
    			t.logger.Error("failed to topic txn", "err", err)
    		}
    	}
    
    	if t.NotifyCh != nil {
    		select {
    		case t.NotifyCh <- struct{}{}:
    		default:
    		}
    	}
    	return nil
    }
    
    func (t *TxPool) addImpl(ctx string, txns ...*types.Transaction) error {
    	if len(txns) == 0 {
    		return nil
    	}
    
    	from := txns[0].From
    	for _, txn := range txns {
    		// Since this is a single point of inclusion for new transactions both
    		// to the promoted queue and pending queue we use this point to calculate the hash
    		txn.ComputeHash()
    
    		err := t.validateTx(txn)
    		if err != nil {
    			return err
    		}
    
    		if txn.From == types.ZeroAddress {
    			txn.From, err = t.signer.Sender(txn)
    			if err != nil {
    				return fmt.Errorf("invalid sender")
    			}
    			from = txn.From
    		} else {
    			// only if we are in dev mode we can accept
    			// a transaction without validation
    			if !t.dev {
    				return fmt.Errorf("cannot accept non-encrypted txn")
    			}
    		}
    
    		t.logger.Debug("add txn", "ctx", ctx, "hash", txn.Hash, "from", from)
    	}
    
    	txnsQueue, ok := t.queue[from]
    	if !ok {
    		stateRoot := t.store.Header().StateRoot
    
    		// initialize the txn queue for the account
    		txnsQueue = newTxQueue()
    		txnsQueue.nextNonce = t.store.GetNonce(stateRoot, from)
    		t.queue[from] = txnsQueue
    	}
    	for _, txn := range txns {
    		txnsQueue.Add(txn)
    	}
    
    	for _, promoted := range txnsQueue.Promote() {
    		t.sorted.Push(promoted)
    	}
    	return nil
    }
    // TxPool is a pool of transactions
    type TxPool struct {
    	logger hclog.Logger
    	signer signer
    
    	store      store
    	idlePeriod time.Duration
    
    	queue map[types.Address]*txQueue
    	sorted *txPriceHeap
    
    	// network stack
    	network *network.Server
    	topic   *network.Topic
    
    	sealing  bool
    	dev      bool
    	NotifyCh chan struct{}
    
    	proto.UnimplementedTxnPoolOperatorServer
    }
    2021-11-04T15:41:07.665+0100 [ERROR] polygon.consensus.dev: failed to write transaction: transaction's gas limit exceeds block gas limit

    Customizable smart contract templates that enable project creators to define the rules and conditions for their token sale and distribution.

  • Access to a community of early adopters and investors.

  • Portfolio management dashboard with P/L tracking and overview.

  • Mobile and web extension compatibility with a consistent user experience.

  • Integration with ZChains and EVM networks for extended functionalities.

  • Security and Performance:

    • Advanced encryption and security protocols.

    • Performance optimization for responsiveness and minimal latency.

  • Currently focusing on ongoing integration with networks compatible with Ethereum Virtual Machine (EVM). However, plans are in place to extend this integration to include a variety of other network types in the future, broadening the scope beyond just EVM-compatible networks. This approach aims to enhance connectivity and interoperability across a diverse range of blockchain networks.
  • Further development of wallet and DEX features based on user feedback and market trends.

  • Enhanced Interoperability and User Experience:

    • Focus on improving cross-network interoperability and user interface enhancements.

  • Community Engagement and New Campaigns:

    • Increasing community involvement in ZChains development.

    • Regular campaigns based on community proposals and voting.

  • Backup & Restore guide
    secp256k1
    key format
    key format
    is the ID of the project the user has defined in GCP console during account setup and Secrets Manager API activation.
  • GCP_CREDS_FILE is the path to the json file containing credentials which will allow read/write access to the Secrets Manager.

  • Managing Private Keys Guide
    GCP Secret Manager
    Local Setup
    Cloud Setup
    Billing Account
    GCP docs
    Secrets Manager API portal
    Configuring Secret Manger
    here
    Local Setup
    Cloud Setup

    NODE_NAME is the name of the current node for which the Vault configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node

    Managing Private Keys Guide
    Hashicorp Vault
    Local Setup
    Cloud Setup
    prerequisites section
    prerequisites section
    Local Setup
    Cloud Setup
    CLI Commands
    # Example private key
    0802122068a1bdb1c8af5333e58fe586bc0e9fc7aff882da82affb678aef5d9a2b9100c0
    polygon-edge secrets generate --type gcp-ssm --dir <PATH> --name <NODE_NAME> --extra project-id=<PROJECT_ID>,gcp-ssm-cred=<GCP_CREDS_FILE>
    polygon-edge secrets init --config <PATH>
    polygon-edge genesis --ibft-validator <VALIDATOR_ADDRESS> ...
    polygon-edge server --secrets-config <PATH> ...
    polygon-edge peers list
    polygon-edge peers status --peer-id <address>
    polygon-edge ibft snapshot
    polygon-edge ibft snapshot --num <block-number>
    polygon-edge ibft candidates
    polygon-edge ibft status
    polygon-edge txpool status
    polygon-edge secrets generate --dir <PATH> --token <TOKEN> --server-url <SERVER_URL> --name <NODE_NAME>
    polygon-edge secrets init --config <PATH>
    polygon-edge genesis --ibft-validator <VALIDATOR_ADDRESS> ...
    polygon-edge server --secrets-config <PATH> ...
    polygon-edge genesis --premine 0x1010101010101010101010101010101010101010
    Zchains genesis --premine 0x1010101010101010101010101010101010101010:0x123123
    polygon-edge server --chain genesis.json --dev --log-level debug
    curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x1010101010101010101010101010101010101010", "latest"],"id":1}' localhost:8545
    {
      "id":1,
      "result":"0x100000000000000000000000000"
    }
    var Web3 = require("web3");
    
    const web3 = new Web3("<provider's websocket jsonrpc address>"); //example: ws://localhost:10002/ws
    web3.eth.accounts
      .signTransaction(
        {
          to: "<recipient address>",
          value: web3.utils.toWei("<value in ETH>"),
          gas: 21000,
        },
        "<private key from premined account>"
      )
      .then((signedTxData) => {
        web3.eth
          .sendSignedTransaction(signedTxData.rawTransaction)
          .on("receipt", console.log);
      });