AnduroProject / alys

5 stars 0 forks source link

Alys

Alys is a merged mined Bitcoin sidechain.

Overview

On a high level, the repository consists of three parts:

Connection Info

Testnet

Alphanet

Prerequisites

git clone git@github.com:AnduroHackathon/Alys.git
cd Alys 

Getting Started

We will describe how to run an Alys sidechain and execute a peg in and out. The sidechain will consist of a single local node, and the federation will have a single member.

Geth and Bitcoin

We will start a single geth node and a Bitcoin regtest node.

# cleanup, init and run geth
./scripts/start_geth.sh
# in a new terminal start bitcoin
bitcoind -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword -fallbackfee=0.002

Alys node

Next, we start a single Alys node with the federation having exactly one member.

# dev (single node)

# From the Alys root directory
cargo run --bin app -- --dev

You will see that the node is producing blocks continuously.

Further details on AuxPoW submission According to the target time of merged mining, eventually, a merged mining block bundle is added to the chain, finalizing the previously created blocks together with the federation's validation and signing of the AuxPoW. In dev mode, we use an embedded miner to get the AuxPoW. When the AuxPoW is not submitted, the node will stop producing blocks. Block production will resume once the next valid AuxPoW is submitted.

Peg-In

Next, we move funds from Bitcoin to Alys via the peg-in to be able to send transactions on the Alys sidechain.

Get the Deposit Address

From the running Alys node, we can get the federation deposit address via the getdepositaddress RPC:

curl --silent -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "getdepositaddress", "params":[]}' http://localhost:3000 | jq -r .result

This returns the federation deposit address of your local Alys node, e.g.:

bcrt1p83w7fnutkcyy5zdknkh3kjah47jg2j6etltpa542wjd6chzj7yksyz0zqw

Send BTC to the Deposit Address

Next, we do a bit of bitcoin-cli magic to create an "Alys" wallet. We send some BTC on regtest from the Alys wallet to the federation deposit address and add an EVM account (0x09Af4E864b84706fbCFE8679BF696e8c0B472201) in an OP_RETURN field for which we know the private key (0xb9176fa68b7c590eba66b7d1894a78fad479d6259e9a80d93b9871c232132c01).

You can run this script to achieve the peg in. The script will automatically fetch the deposit address from the federation nodes.

# set the btc amount and evm address
EVM_ADDRESS="09Af4E864b84706fbCFE8679BF696e8c0B472201"
./scripts/regtest_pegin.sh "1.0" $EVM_ADDRESS

# OR use the $DEV_PRIVATE_KEY
./scripts/regtest_pegin.sh

The Alys node will automatically bridge the BTC.

Check that Funds are Allocated inAlys

Run cast to check that the funds have been allocated. Note that on peg-in, satoshis (10^8) will be converted to wei (10^18) so you will see a lot more 0s for the bridge 1 BTC, i.e., 1 10^18 wei instead of 1 10^8 satoshis.

cast balance 0x09Af4E864b84706fbCFE8679BF696e8c0B472201 --rpc-url "localhost:8545"
> 1000000000000000000

Peg-Out

Next up, we want to peg out.

Peg-out Funds

We are returning the funds to the Alys wallet we created in Bitcoin.

We can use the peg out contract set the genesis at address 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB, see also the genesis file.

We are doing this from the CLI and will need to define a PRIVATE_KEY env.

# set the private key and btc address
PRIVATE_KEY=0xb9176fa68b7c590eba66b7d1894a78fad479d6259e9a80d93b9871c232132c01
./scripts/regtest_pegout.sh $PRIVATE_KEY $BTC_ADDRESS

# OR just the private key
./scripts/regtest_pegout.sh $PRIVATE_KEY

# OR use the $DEV_PRIVATE_KEY
./scripts/regtest_pegout.sh

# check the last 3 transactions. The 2 last should be the mining reward to alys (with category "immature") and the 3rd last txs should be a normal receive tx from the foundation
bitcoin-cli -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword listtransactions "*" 3
Expected output ```shell { "address": "bcrt1qane4k9ejhhca9w0ez7ale7xru5pnrqmuwqayhc", "parent_descs": [ "wpkh(tpubD6NzVbkrYhZ4XGc5eHTPRieN8p27r6PPNenUPJz5JQeCkav8aZ2wz9zc83xgEUVbpQetH6FXABUZ5LDG9uDWqf7fc9RN2yfJzDAmHnSFHHw/84h/1h/0h/0/*)#t9fj9n6e" ], "category": "receive", "amount": 0.00010000, "label": "", "vout": 0, "abandoned": false, "confirmations": 2, "blockhash": "78e3a9699277e9dc1da0da5e7f47bded9abdfce673bf1858e18aa6c2089d7d54", "blockheight": 792, "blockindex": 1, "blocktime": 1706691489, "txid": "831094cba680a5cbbd622b464eaf69562d53b681400c747cee72caddbc9765b4", "wtxid": "0dca63f31e7b873ef29d5ea3124a62f7e40d9f9de5b72e88c39904e9e6750256", "walletconflicts": [ ], "time": 1706691488, "timereceived": 1706691488, "bip125-replaceable": "no" }, ```

Running an Alys Network

Above, we show how to run Alys with a single node and a single federation member. The dev setup is meant for ease of testing without having to set various parameters. For operating an Alys network with multiple federation members, we need to take more steps.

We will now run an Alys network with three federation members where each member will run a geth and Alys consensus client and share a Bitcoin node.

In practice, each federation member will run their own Bitcoin node.

Keys

Each Alys node will require two keys:

An EVM account is also needed to receive fees, but the private key does not need to be supplied.

Generate Secure Keys

There are multiple ways to generate secure keys. We will not cover the different options here and trust operators to have an understanding of how to generate and protect secret keys.

Note: if you are choosing to create keys, the chain configuration file needs to be adjusted as described here: Chain spec.

Developer Keys

For development purposes on private test deployments, we recommend using dummy keys:

Testing Keys (Aura/EVM)
Secret Key: 0000000000000000000000000000000000000000000000000000000000000001
Public Key: 97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb
Address: 7e5f4552091a69125d5dfcb7b8c2659029395bdf

Secret Key: 0000000000000000000000000000000000000000000000000000000000000002
Public Key: a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e
Address: 2b5ad5c4795c026514f8317c7a215e218dccd6cf

Secret Key: 0000000000000000000000000000000000000000000000000000000000000003
Public Key: 89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224
Address: 6813eb9362372eef6200f3b1dbc3f819671cba69
Testing Keys (Bitcoin)
Secret Key: 0000000000000000000000000000000000000000000000000000000000000001
Public Key: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

Secret Key: 0000000000000000000000000000000000000000000000000000000000000002
Public Key: 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5

Secret Key: 0000000000000000000000000000000000000000000000000000000000000003
Public Key: 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9

Start Bitcoin

Regtest

bitcoind -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword -fallbackfee=0.002
bitcoin-cli -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword createwallet alys
bitcoin-cli -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword loadwallet alys
bitcoin-cli -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword generatetoaddress 101 $(bitcoin-cli -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword -rpcwallet=alys getnewaddress)

Testnet

Note: this will sync the entire Bitcoin test network.

bitcoind -testnet -rpcuser=rpcuser -rpcpassword=rpcpassword

Mainnet

Note: this will sync the entire Bitcoin main network.

bitcoind -rpcuser=rpcuser -rpcpassword=rpcpassword

Run Three Nodes

Each Alys node takes several arguments:

In our example, we are going to use dev keys. These can be replaced by generating custom keys as described above.

We need six terminals, two for each node.

All Networks

Three geth nodes:

NUM=0 ./scripts/start_geth.sh
NUM=1 ./scripts/start_geth.sh
NUM=2 ./scripts/start_geth.sh

Regtest

Node 1

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000001 \
  --geth-url http://localhost:8551/ \
  --db-path etc/data/consensus/node_0/chain_db/ \
  --rpc-port 3000 \
  --mine \
  --wallet-path etc/data/consensus/node_0/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000001 \
  --bitcoin-rpc-url localhost:18443 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword

Node 2

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000002 \
  --geth-url http://localhost:8561/ \
  --db-path etc/data/consensus/node_1/chain_db/ \
  --rpc-port 3001 \
  --mine \
  --wallet-path etc/data/consensus/node_1/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000002 \
  --bitcoin-rpc-url localhost:18443 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword

Node 3

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000003 \
  --geth-url http://localhost:8571/ \
  --db-path etc/data/consensus/node_2/chain_db/ \
  --rpc-port 3002 \
  --mine \
  --wallet-path etc/data/consensus/node_2/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000003 \
  --bitcoin-rpc-url localhost:18443 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword

Testnet (with the dummy miner)

Node 1

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000001 \
  --geth-url http://localhost:8551/ \
  --db-path etc/data/consensus/node_0/chain_db/ \
  --rpc-port 3000 \
  --mine \
  --wallet-path etc/data/consensus/node_0/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000001 \
  --bitcoin-rpc-url localhost:18332 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword \
  --bitcoin-network testnet

Node 2

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000002 \
  --geth-url http://localhost:8561/ \
  --db-path etc/data/consensus/node_1/chain_db/ \
  --rpc-port 3001 \
  --mine \
  --wallet-path etc/data/consensus/node_1/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000002 \
  --bitcoin-rpc-url localhost:18332 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword \
  --bitcoin-network testnet

Node 3

cargo run --bin app -- \
  --chain etc/config/chain.json \
  --aura-secret-key 0000000000000000000000000000000000000000000000000000000000000003 \
  --geth-url http://localhost:8571/ \
  --db-path etc/data/consensus/node_2/chain_db/ \
  --rpc-port 3002 \
  --mine \
  --wallet-path etc/data/consensus/node_2/wallet \
  --bitcoin-secret-key 0000000000000000000000000000000000000000000000000000000000000003 \
  --bitcoin-rpc-url localhost:18332 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword \
  --bitcoin-network testnet

Mining

Note the --mine flag used above which enables an auto-miner that attempts to find and submit a solution for the PoW.

We also provide an RPC that provides two methods, createauxblock and submitauxblock, based on the Namecoin implementation. If you are a mining pool operator, use the default RPC port (3000, or configure --rpc-port) to fetch the target and hash from your authority or full-node and then submit the AuxPow.

Note as per the Marathon specs we currently check the third or fourth output of the coinbase transaction to extract the merged mining header, this differs from the original specification here (and corresponding implementations) which expect this in the scriptSig of the first input. It is also assumed that this is the only data in the scriptPubKey and there are no other instructions or data pushes. Additionally, Marathon does not yet support the merkle construction so the header always contains the AuxPow hash instead of the merkle root.

For local development we also offer a minimal binary named miner that does the same thing as the in-process service but over RPC.

cargo run --bin miner -- --url "127.0.0.1:3000"

Peg in and Peg out

The peg in and out procedures are the same as in the Getting Started guide above.

The main difference will be the changed federation deposit address, which is bcrt1pyjrtzxpj7vzpjz6kvps5c2ajhkupt8qk22ncdpdz23wdl5j7dp8qexkr43 if using the dev keys.

Full Node

Running a full node is similar to running a federation node. The main difference is that full nodes do not require an Aura or Bitcoin key since they are not signing blocks or Bitcoin transactions.

  1. Start Bitcoin with the network you want to connect to via bitcoind.
  2. Start geth via NUM=0 ./scripts/start_geth.sh (assuming you run the full node on a different machine. If running on the same machine as the three nodes, use NUM=3 ./scripts/start_geth.sh)
  3. Start the full node assuming the full node runs on a seperate machine:
cargo run --bin app -- \
  --chain etc/config/chain.json \ 
  --geth-url http://localhost:8551/ \
  --db-path etc/data/consensus/node_0/chain_db/ \
  --rpc-port 3000 \
  --wallet-path etc/data/consensus/node_0/wallet \
  --bitcoin-rpc-url localhost:18332 \
  --bitcoin-rpc-user rpcuser \
  --bitcoin-rpc-pass rpcpassword \
  --bitcoin-network testnet \
  --remote-bootnode /ip4/BOOTNODE_IP/tcp/BOOTNODE_LIBP2P_PORT

Development

Alys Node (Consensus Layer)

Build and Deploy

# cleanup, init and run geth
./scripts/start_geth.sh

# start bitcoin
bitcoind -regtest -rpcuser=rpcuser -rpcpassword=rpcpassword -fallbackfee=0.002

# dev (single node)
cargo run --bin app -- --dev

Testnet

Node IP: 107.20.115.193
Node Port: 8545

Faucet

https://faucet.anduro.io/

Unit tests

Tests are self-contained such that none of the services need to run.

cargo test

Format

cargo fmt

Smart Contracts

Build and Deploy

Go to the contracts folder.

cd ./contracts

The contracts folder contains only the bridge contract for the peg out. However, you can add any other smart contracts you may wish to add here.

Build and deploy.

forge build

Example ERC20

We are going to deploy an example ERC20 contract to show how to interact with the sidechain.

We are going to use our private key (0xb9176fa68b7c590eba66b7d1894a78fad479d6259e9a80d93b9871c232132c01) as a means to deploy the contract. Make sure the account belonging to this key has received funds via the peg-in procedure.

PRIVATE_KEY=0xb9176fa68b7c590eba66b7d1894a78fad479d6259e9a80d93b9871c232132c01
# constructor takes the name of the contract, the ticker, and the initial supply that is minted to the creator of the contract
forge create --rpc-url "http://127.0.0.1:8545" --private-key ${PRIVATE_KEY} src/MockErc20.sol:MockErc20 --json --constructor-args "HelloBitcoinContract" "HBC" 100000000000000000000000

This should result in something like:

{"deployer":"0x09Af4E864b84706fbCFE8679BF696e8c0B472201","deployedTo":"0x1C36129916E3EA2ACcD516Ae92C8f91deF7c4146","transactionHash":"0x8478bbed6ba658eecb8e36c143969cf6c11c4517f5f32acf75af5a9c41ac69dd"}

Other useful scripts:

# Send some of the ERC20 tokens from the deployed contract (0x1C36129916E3EA2ACcD516Ae92C8f91deF7c4146) to account 0xd362E49EE9453Bf414c35288cD090189af2B2C55
cast send --private-key ${PRIVATE_KEY} \
  --rpc-url "localhost:8545" \
  --chain 263634 \
  0x1C36129916E3EA2ACcD516Ae92C8f91deF7c4146 \
  "transfer(address,uint256)" 0xd362E49EE9453Bf414c35288cD090189af2B2C55 100000000
# Send 16200000000007550 wei bridged BTC to account 0xd362E49EE9453Bf414c35288cD090189af2B2C55
cast send --private-key ${PRIVATE_KEY} 0xd362E49EE9453Bf414c35288cD090189af2B2C55 --value 16200000000007550

Test

forge test

Format

forge fmt

EVM Tooling

Since we use Geth without modification, it is already possible to use most existing EVM tooling out-the-box including MetaMask, Foundry / Hardhat and of course Blockscout!

Blockscout

To setup Blockscout follow the deployment guides here. We recommend using Docker Compose for simplicity.

git clone git@github.com:blockscout/blockscout.git
cd ./docker-compose

Change the environment variables:

# /docker-compose/envs/common-blockscout.yml
SUBNETWORK=Merged ALYS
CHAIN_ID=263634
# /docker-compose/envs/common-frontend.yml
NEXT_PUBLIC_NETWORK_NAME=Merged ALYS Alpha  
NEXT_PUBLIC_NETWORK_SHORT_NAME=Merged ALYS Alpha

Start the explorer with:

docker-compose -f geth.yml up --build

The explorer runs on localhost:80.

If you reset the chain make sure to clear the persistent data in docker-compose/services/.

sudo rm -rf services/redis-data services/stats-db-data services/blockscout-db-data services/logs

Genesis

We provide genesis.json for local development using Geth but it is also possible to use this other deployments.

It was previously based on the Sepolia genesis with some modifications using this guide:

geth --sepolia dumpgenesis | jq .

Ensure that the chain is configured to start post-capella (set shanghaiTime to 0).

The Alys sidechain expects the bridge contract to be pre-deployed at 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB, this is set in alloc.

Chain Spec

When you start the Alys sidechain it will use a chain spec to configure it's own genesis block based also on the Geth genesis configured above. We provide chain.json for local development assuming three nodes (instructions above) or using --chain=dev will start a single node network. See the annotations below for how to configure a new setup:

{
    // the block duration in milliseconds
    "slotDuration": 2000,
    // public keys for bls signing
    "authorities": [],
    // evm addresses for each authority (to receive fees)
    "federation": [],
    // public keys for secp256k1 signing
    "federationBitcoinPubkeys": [],
    // initial PoW mining difficulty
    "bits": 553713663,
    // should be the same as the geth `genesis.json`
    "chainId": 263634,
    // stall block production if no AuxPow is received
    "maxBlocksWithoutPow": 10,
    // set the scanning height, use latest height for testnet or mainnet
    "bitcoinStartHeight": 0,
    "retargetParams": {
        // disable retargeting so we always keep the same target
        "powNoRetargeting": false,
        // the maximum target allowed
        "powLimit": 553713663,
        // expected difficulty adjustment period (in seconds)
        "powTargetTimespan": 12000,
        // expected block time (in seconds)
        "powTargetSpacing": 1000
    }
}

Each node should use the same genesis and chain spec, otherwise blocks will be rejected.

Ensure that each federation member has set an EVM address to receive fees - this can be derived from the same secret key used to generate the public key in "authorities". When fees are generated from EVM transactions they are sent directly to that account.

Resources