This is an elixir full node implementation of the aeternity specification.
A Dockerfile
and docker-compose.yml
are found in the base directory, prebuilt images are not yet published.
-
Build container
docker build . -t elixir-node
-
Run node in container
docker run --name elixir-node -it -p 4000:4000 elixir-node
-
Run multiple nodes network with docker compose
docker-compose up
runs 3 connected nodes, with 2 mining
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
To install and use the Elixir Blockchain you will need Elixir, Rust (for RocksDB persistence) and the source code by cloning or downloading the repository.
Make sure you have installed the following packages to make sure that the Wallet will work properly:
sudo apt-get install autoconf autogen libtool libgmp3-dev
wget -O libsodium-src.tar.gz https://github.com/jedisct1/libsodium/releases/download/1.0.16/libsodium-1.0.16.tar.gz
mkdir libsodium-src && tar -zxf libsodium-src.tar.gz -C libsodium-src --strip-components=1
cd libsodium-src && ./configure && make && make check && sudo make install && cd ..
mix deps.get
Start the application in interactive Elixir mode
iex -S mix phx.server
To start the miner use the following command in the command prompt:
Miner.resume()
This will continuously mine new blocks until terminated or suspended. To suspend/stop the miner from mining:
Miner.suspend()
To build a custom transaction you need to follow few simple steps:
- Make your own
transaction module
- Create your
custom transaction structure
- Override the
Transaction Behaviour
callbacks - Write all your specific functions and checks inside your new
Transaction module
All custom transactions are childs to the DataTx
Transaction that wraps them inside.
The DataTx strucure hold:
- The name of your
transaction type
that should be youTransaction Module name
- The
payload
that will hold yourcustom transaction structure
sender
,fee
andnonce
-
To get the top height of the current chain:
Chain.top_height()
-
To add get the top block of the current chain:
Chain.top_block()
-
To get the top block chain state:
Chain.top_block_chain_state()
-
To get the top block hash:
Chain.top_block_hash()
-
To get the latest chainstate:
Chain.chain_state()
-
To get the chainstate of certain block:
Chain.chain_state(block_hash)
-
To get a block from the local chain with certain hash
Chain.get_block_by_hex_hash(hash_of_a_block)
-
To get a block from memory or the database by certain hash:
Chain.get_block(hash_of_a_block)
-
To check if a certain block is in the local chain:
Chain.hash_block?(hash_of_a_block)
-
To get a certain number of blocks:
Chain.get_blocks(start_block_hash, number_of_blocks)
-
To get the longest chain:
Chain.longest_blocks_chain()
-
To get a candidate block:
Miner.candidate()
-
To get the current value of the coinbase:
Miner.coinbase_transaction_value()
-
To get the current state of the miner:
Miner.get_state()
The default sync port is 3015, it can be set manually by running the node with SYNC_PORT=some_port iex -S mix
. This port is different from the one used by the phoenix server application.
-
To get all peers:
Peers.all_peers()
-
Connect to a peer by specifying the address (host), port (SYNC_PORT) and peer pubkey (different from the keypair which is used for transaction signing):
Peers.try_connect(%{host: host, port: port, pubkey: pubkey})
-
To inspect all transactions in the Transaction Pool:
Pool.get_pool()
-
The node will run an http API at:
localhost:4000
-
To get the current info of the node:
GET localhost:4000/info
-
To get the peers your node is connected to:
GET localhost:4000/peers
-
To get all blocks from the current chain:
GET localhost:4000/blocks
-
To get all blocks with full information about the blocks:
GET localhost:4000/raw_blocks
-
To get the transactions in the Transaction Pool:
GET localhost:4000/pool_txs
-
To post new transaction to the Transaction Pool:
POST localhost:4000/new_tx
Body: serialized_tx
Where serialized_tx is json serialized signed transaction structure
-
To post new block to the chain:
POST localhost:4000/block
Body: serialized_block
Where serialized_block is a json serialized block structure
-
To get all transactions for an account
GET localhost:4000/tx/{account}
Where account is a hex encoded public key
-
To get a block by hash
GET localhost:4000/block/{block_hash}
Where block_hash is the bech32 encoded block header hash
-
To get an account balance:
GET localhost:4000/balance/{account}
Where account is a hex encoded public key
-
To get the transaction pool of an account:
GET localhost:4000/tx_pool/{account}
Where account is a hex encoded public key
To run the automatic tests:
mix test
To debug, see errors, warnings and info about the blockchain,
the log can be found in the source folder under:apps/aecore/logs
09:59:16.298 [info] Added block #1 with hash 6C449AC3B5E38857DC85310873979F45992270BF54304B3F60BE4F64373991B5, total tokens: 100
09:59:16.298 [info] Mined block #1, difficulty target 1, nonce 4
TTL (time to live): Determines the lifetime of an object measured in blocks, it can be either absolute or relative (absolute - the object is removed when a block with a height equal to the TTL is mined; relative - the object is removed when a block with height equal to the TTL + block height in which the transaction was included is mined). A TTL is defined like so - %{ttl: value, type: :relative | :absolute}
Registering an oracle:
Oracle.register(query_format, response_format, query_fee, fee, ttl)
The query and response formats are treated as json schemas by which the queries and responses
are validated. The query fee is the minimum fee that will be required for queries made to the
oracle. If the oracle responds to the query on time, he will receive that fee as a compensation
for the response fee he had to pay.
example format schema -
%{ "type" => "object", "properties" => %{ "base" => %{ "type" => "string" }, "date" => %{ "type" => "string" }, "rates" => %{ "type" => "object" } } }
To list all registered oracles - Chain.registered_oracles()
Querying an oracle:
Oracle.query(oracle_address, query_data, query_fee, fee, query_ttl, response_ttl)
The query TTL determines the time in which the oracle is able to respond, if he doesn't respond in time, the account making the query will get the query_fee back. The response TTL determines for how long the response will remain in the state after it is added, this TTL can only be relative.
example query -
Oracle.query(
"ak$5oyDtV2JbBpZxTCS5JacVfPQHKjxCdoRaxRS93tPHcwvqTtyvz",
%{
"currency" =>
"USD"
},
5,
10,
%{ttl: 10, type: :absolute},
%{ttl: 5, type: :relative}
)
Respond to a query:
Oracle.respond(query_id, response, fee)
Responds to a query which is referenced by ID, if the response is added successfully, the oracle receives the query_fee contained in the query.
Extend a registered oracle:
Oracle.extend(ttl, fee)
Extends the TTL of an oracle with the address that matches the address of the node.
All transactions have to be mined in order to take effect.
Names will follow IDNA2008 normalization and have a maximum length of 253 characters, while each label is allowed to be 63 characters maximum. Names must end with .aet
or .test
.
NamePreClaim
a name, to register your interest in claiming it, while not announcing what name, a private binary salt is chosen.Account.pre_claim(name, salt, fee)
NameClaim
is possible after one block to publicly claim the name by setting the ownerAccount.claim(name, salt, fee)
. Claims expire after 50000 blocks, if not renewed using update.NameUpdate
updates associated pointers to one registered name, while updating the expiry.Account.name_update(name, pointers, fee)
NameTransfer
transfers one account claim to a different owner.Account.name_transfer(name, target, fee)
NameRevoke
revokes one name claim, will result in deletion after 2016 blocks.Account.name_revoke(name, fee)