Skip to content

Commit

Permalink
Merge branch 'main' into feat/gas-estimation
Browse files Browse the repository at this point in the history
  • Loading branch information
re1ro authored Mar 28, 2024
2 parents d2fce96 + 0bc4597 commit 997d356
Show file tree
Hide file tree
Showing 16 changed files with 671 additions and 12 deletions.
187 changes: 187 additions & 0 deletions examples/amplifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
## Introduction

This repo provides the code for interacting with the Amplifier Relayer API to relay transactions to the Axelar network and listen to Axelar events.

For a visual of the flow of an outgoing message see [outgoing msg](/images/Outgoing-Relayer.png)
For a visual of the flow of an inbound message see [inbound msg](/images/Inbound-Relayer.png)

## Setup

1. Clone this repo.
1. Install dependencies:
```bash
npm install
```
1. Copy `.env.example` into `.env` and set up the following environment variables:
```bash
HOST=...
PORT=...
```

## Generic endpoints

There are two endpoints that can be used for generic commands and events:

1. `broadcast` -- Sends a command to get executed as a wasm message on the network
1. `subscribe-to-wasm-events` -- Subscribes to all wasm events emitted on the network

### `broadcast`

To broadcast a command, use the following:

```bash
$ node amplifier broadcast \
--address <destination contract> \
--payload <execute message>
```
For example, call `distribute_rewards()` on the `Rewards` contract to distribute rewards:

```bash
$ node amplifier broadcast \
--address axelar1wkwy0xh89ksdgj9hr347dyd2dw7zesmtrue6kfzyml4vdtz6e5ws2pvc5e \
--payload '{"distribute_rewards":{"pool_id":{"chain_name":"fantom","contract":"axelar1ufs3tlq4umljk0qfe8k5ya0x6hpavn897u2cnf9k0en9jr7qarqqa9263g"},"epoch_count":1000}}'
Broadcasting message:
axelar1wkwy0xh89ksdgj9hr347dyd2dw7zesmtrue6kfzyml4vdtz6e5ws2pvc5e {"distribute_rewards":{"pool_id":{"chain_name":"fantom","contract":"axelar1ufs3tlq4umljk0qfe8k5ya0x6hpavn897u2cnf9k0en9jr7qarqqa9263g"},"epoch_count":1000}}
Connecting to server at localhost:50051
Message sent for broadcast
```

### `subscribe-to-wasm-events`

To get all wasm events emitted on the Axelar network, run:

```bash
node amplifier subscribe-to-wasm-events
```

You can optionally specify a `start-height` to catch events that were emitted at a previous time with the `--start-height` flag. It is set to `0` by default, which means that subscription starts from the current tip of the chain:

```
$ node amplifier subscribe-to-wasm-events --start-height 221645
Subscribing to events starting from block: 221645
Connecting to server at localhost:50051
Event: {
type: 'wasm-voted',
attributes: [
{
key: '_contract_address',
value: 'axelar1466nf3zuxpya8q9emxukd7vftaf6h4psr0a07srl5zw74zh84yjq4687qd'
},
{ key: 'poll_id', value: '"1"' },
{
key: 'voter',
value: 'axelar1hzy33ue3a6kztvfhrv9mge45g2x33uct4ndzcy'
}
],
height: Long { low: 221645, high: 0, unsigned: true }
}
```

Every event includes a `type` field that specifies the type of the event, an `attributes` field with all relevant information, and a `height` event that specifies the height emitted.

## General Message Passing

The following endpoints are available to facilitate GMP calls:

1. `verify` -- triggers a verification on the source chain (routing is handled automatically)
2. `subscribe-to-approvals` -- creates a channel to return all calls that are approved on the destination chain
3. `get-payload` -- queries the payload of the initial source-chain transaction by its hash

### `verify`

Given a transaction relayed on the source chain, the `verify` command is called as follows:

``` bash
node amplifier verify \
--id 0x02293467b9d6e1ce51d8ac0fa24e9a30fb95b5e1e1e18c26c8fd737f904b564c:4 \
--source-chain avalanche \
--source-address 0x90AD61b0FaC683b23543Ed39B8E3Bd418D6CcBfe \
--destination-chain fantom \
--destination-address 0x9B35d37a8ebCb1d744ADdEC47CA2a939e811B638 \
--payload 00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000f68656c6c6f206176616c616e63686500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

where

- `id` -- the `<transactionHash>:<blockLogIndex>`. Note that you need the `blockLogIndex`, not the `txLogIndex`. For example, the [earlier transaction](https://testnet.snowtrace.io/tx/0x02293467b9d6e1ce51d8ac0fa24e9a30fb95b5e1e1e18c26c8fd737f904b564c) is included in [`block 31050074`](https://testnet.snowtrace.io/block/31050074?chainId=43113), and the `ContractCall` topic is `0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae`. Searching for this topic in the block’s logs, we see that the `logIndex` is `4` :

```bash
$ curl -s --location $RPC \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{
"fromBlock": "0x1d9c95a"
}],"id":1}' | jq | grep 0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae -A 11 -B 3
{
"address": "0xca85f85c72df5f8428a440887ca7c449d94e0d0c",
"topics": ["0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae", "0x00000000000000000000000090ad61b0fac683b23543ed39b8e3bd418d6ccbfe", "0xa9b070ad799e19f1166fdbf4524b684f8026df510fe6a7770f949ad54047098c"],
...
"logIndex": "0x4", # <- our logIndex
...
},
```
- `source-chain` -- the source chain
- `source-address` -- the address of the sender
- `destination-chain` -- the destination chain
- `destination-address` -- the address of the recipient
- `payload` -- the transaction payload of `ContractCall` event, in bytes. The `0x` can be omitted:

![Payload](/images/payload.png)

After a few seconds, the `verify` command will exit displaying the `id`, and or an error if any:

```bash
node amplifier verify --id 0x02293467b9d6e1ce51d8ac0fa24e9a30fb95b5e1e1e18c26c8fd737f904b564c:4 --source-chain avalanche --source-address 0x90AD61b0FaC683b23543Ed39B8E3Bd418D6CcBfe --destination-chain fantom --destination-address 0x9B35d37a8ebCb1d744ADdEC47CA2a939e811B638 --payload 00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000f68656c6c6f206176616c616e63686500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Connecting to server at localhost:50051
Verifying message: {
message: {
id: '0x02293467b9d6e1ce51d8ac0fa24e9a30fb95b5e1e1e18c26c8fd737f904b564c:4',
sourceChain: 'avalanche',
sourceAddress: '0x90AD61b0FaC683b23543Ed39B8E3Bd418D6CcBfe',
destinationChain: 'fantom',
destinationAddress: '0x9B35d37a8ebCb1d744ADdEC47CA2a939e811B638',
payload: <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 110 more bytes>
}
}
Success verification for 0x02293467b9d6e1ce51d8ac0fa24e9a30fb95b5e1e1e18c26c8fd737f904b564c:4
```

### `subscribe-to-approvals`

After a verification is initiated and once all internal processes (verifying, routing messages to the destination gateway, and constructing proof) are done on the Axelar network, a `signing-completed` event is emitted which contains a `session-id`. This `session-id` can be used to query the proof from the Axelar chain and return the execute data that need to be relayed on the destination chain. Do this by running `subscribe-to-approvals`:

```bash
node amplifer subscribe-to-approvals \
--chain fantom \
--start-height <start-height> # optional
```

- `chain` -- the destination chain
- `start-height` (optional) -- start height [0 = latest] similar to `subscribe-to-wasm-events`

For example:

```bash
$ node amplifier subscribe-to-approvals -c fantom -s 221645
Subscribing to approvals starting from block: 221645 on chain: fantom
Connecting to server at localhost:50051
chain: fantom
block height: 221855
execute data: 09c5eabe000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000fa2000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010749a63dd8ad2d24037397e5adff4027176863d46a05e007749e3d9b2e1eadb3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000013617070726f7665436f6e747261637443616c6c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000009b35d37a8ebcb1d744addec47ca2a939e811b638a9b070ad799e19f1166fdbf4524b684f8026df510fe6a7770f949ad54047098c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096176616c616e6368650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307839304144363162304661433638336232333534334564333942384533426434313844364363426665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000030000000000000000000000008054f16ad10c3bf57e178f7f9bc45ea89f84301a00000000000000000000000089a73afebb411c865074251e036d4c12eb99b7ba000000000000000000000000f330a7f2a738eefd5cf1a33211cd131a7e92fdd400000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000417d7349a27e2a6e291f54a5954ec32eb7dcb6f5ec33fe71830dac34181d8af97b6d1d5f2d1309a0c56820cf95f6d4890444e35c8cdb749bf3f2d1c69393c1a2661b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041bd88d12035d8b2aededcf1444ecba29dd933087761e8ea1fd6c0d7efb0262542240539234dcf72c2ba748b9ece101c365fd498dfd565f646249771f56ade281f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041af4a7296bcb0b21282a7938c7a97ec1f82c2fe440e260e0dbe8aeecfaa5c77a902bc39a6dea0235860944a3085ff6e9a1c340865e9f93d82a8a4922d0c5253fb1b00000000000000000000000000000000000000000000000000000000000000
---
```

### `get-payload`

To get the payload that was submitted by the transaction on the source chain, use `get-payload`:

```bash
$ node amplifier get-payload --hash 0xa9b070ad799e19f1166fdbf4524b684f8026df510fe6a7770f949ad54047098c
Getting payload for payload hash a9b070ad799e19f1166fdbf4524b684f8026df510fe6a7770f949ad54047098c
Connecting to server at localhost:50051
Payload:
00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000f68656c6c6f206176616c616e63686500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

- `hash` -- the payload hash

![Payload hash](/images/payload-hash.png)
52 changes: 52 additions & 0 deletions examples/amplifier/amplifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const commander = require('commander');
const { broadcast } = require('./endpoints/broadcast.js');
const { getPayload } = require('./endpoints/get-payload.js');
const { subscribe_to_approvals } = require('./endpoints/subscribe-to-approvals.js');
const { subscribe_to_wasm_events } = require('./endpoints/subscribe-to-wasm-events.js');
const { verify } = require('./endpoints/verify.js');

const program = new commander.Command();

program
.command('broadcast')
.requiredOption('-a, --address <contract address>', 'The address of the destination contract')
.requiredOption("-p, --payload <payload>", "The payload of the wasm message")
.action((options) => {
broadcast(options.address, options.payload);
});

program
.command('get-payload')
.requiredOption('--hash, <hash>', 'payload hash')
.action((options) => {
getPayload(options.hash);
});

program
.command('subscribe-to-approvals')
.requiredOption("-c, --chain <chain>", "The chain to subscribe to")
.option("-s, --start-height <start height>", "The block height to start from (0 = latest)", parseInt, 0)
.action((options) => {
subscribe_to_approvals(options.chain, options.startHeight);
});

program
.command('subscribe-to-wasm-events')
.option("-s, --start-height <start height>", "The block height to start from (0 = latest)", parseInt, 0)
.action((startHeight) => {
subscribe_to_wasm_events(startHeight)
});

program
.command('verify')
.requiredOption("-i, --id <transaction id>", "The id of the transaction (txHash:logIndex)")
.requiredOption("--source-chain <source chain>", "The source chain")
.requiredOption("--source-address <source address>", "The source address")
.requiredOption("--destination-chain <destination chain>", "The destination chain")
.requiredOption("--destination-address <destination address>", "The destination address")
.requiredOption("--payload <payload>", "The GMP payload in hex")
.action((options) => {
verify(options.id, options.sourceChain, options.sourceAddress, options.destinationChain, options.destinationAddress, options.payload);
});

program.parse();
94 changes: 94 additions & 0 deletions examples/amplifier/amplifier.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
syntax = "proto3";
package axelar.amplifier.v1beta1;

// import "google/api/annotations.proto";

option go_package = "github.com/axelarnetwork/axelar-eds/pkg/amplifier/server/api";

service Amplifier {
rpc Verify(stream VerifyRequest) returns (stream VerifyResponse);
rpc GetPayload(GetPayloadRequest) returns (GetPayloadResponse) {
option (google.api.http) = {
get : "/v1beta1/payload/{hash}"
};
}
rpc SubscribeToApprovals(SubscribeToApprovalsRequest)
returns (stream SubscribeToApprovalsResponse);
rpc SubscribeToWasmEvents(SubscribeToWasmEventsRequest)
returns (stream SubscribeToWasmEventsResponse);
rpc Broadcast(BroadcastRequest) returns (BroadcastResponse) {
option (google.api.http) = {
post : "/v1beta1/broadcast"
body : "*"
};
}
}

message Message {
string id = 1; // the unique identifier with which the message can be looked
// up on the source chain
string source_chain = 2;
string source_address = 3;
string destination_chain = 4;
string destination_address = 5;
bytes payload = 6;
// when we have a better idea of the requirement, we can add an additional
// optional field here to facilitate verification proofs
}

message GetPayloadRequest { bytes hash = 1; }

message GetPayloadResponse { bytes payload = 1; }

message SubscribeToApprovalsRequest {
repeated string chains = 1;
optional uint64 start_height = 2; // can be used to replay events
}

message SubscribeToApprovalsResponse {
string chain = 1;
bytes execute_data = 2;
uint64 block_height = 3;
}

message VerifyRequest { Message message = 1; }

message VerifyResponse {
Message message = 1;
// bool success = 2;
optional Error error = 2;
}

enum ErrorCode {
VERIFICATION_FAILED = 0;
INTERNAL_ERROR = 1;
AXELAR_NETWORK_ERROR = 2;
INSUFFICIENT_GAS = 3;
}

message Error {
string error = 1;
ErrorCode error_code = 2;
}

message SubscribeToWasmEventsRequest { optional uint64 start_height = 1; }

message SubscribeToWasmEventsResponse {
string type = 1;
repeated Attribute attributes = 2;
uint64 height = 3;
}

message Attribute {
string key = 1;
string value = 2;
}

message BroadcastRequest {
string address = 1;
bytes payload = 2;
}

message BroadcastResponse {
bool result = 1;
}
22 changes: 22 additions & 0 deletions examples/amplifier/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const dotenv = require('dotenv');

// Load environment variables from .env file
dotenv.config();

// Default configuration values
const defaults = {
HOST: "localhost",
PORT: "50051"
};

function getConfig() {
const serverHOST = process.env.HOST || defaults.HOST;
const serverPort = process.env.PORT || defaults.PORT;

return {
serverHOST,
serverPort
};
}

module.exports = getConfig;
27 changes: 27 additions & 0 deletions examples/amplifier/endpoints/broadcast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const newClient = require('../grpc/client');

function broadcast(address, payload) {
console.log("Broadcasting message:\n", address, payload);

try {
JSON.parse(payload);
} catch (e) {
console.error("Payload is not valid JSON");
process.exit(1);
}

const client = newClient();
const broadcastRequest = { address, payload: Buffer.from(payload) };
response = client.Broadcast(broadcastRequest, (err, response) => {
if (err) {
console.error("Error", err);
} else {
console.log("Message sent for broadcast");
process.exit(0);
}
});
}

module.exports = {
broadcast,
};
Loading

0 comments on commit 997d356

Please sign in to comment.