Migrating to NEAR Lake Framework
We encourage everyone who don't have a hard requirement to use NEAR Indexer Framework consider the migration to NEAR Lake Framework.
In this tutorial we'll show you how to migrate the project using indexer-tx-watcher-example as a showcase.
The source code for the migrated indexer can be found on GitHub https://github.com/near-examples/indexer-tx-watcher-example-lake/tree/0.4.0
We've posted the diffs for the reference in the end of the article, you can scroll down to them if diffs are all you need in order to migrate your indexer
Changing the dependencies
First of all we'll start from the dependencies in Cargo.toml
[package]
name = "indexer-tx-watcher-example"
version = "0.1.0"
authors = ["Near Inc <hello@nearprotocol.com>"]
edition = "2018"
[dependencies]
actix = "=0.11.0-beta.2"
actix-rt = "=2.2.0" # remove it once actix is upgraded to 0.11+
base64 = "0.11"
clap = "3.0.0-beta.1"
openssl-probe = { version = "0.1.2" }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.55"
tokio = { version = "1.1", features = ["sync"] }
tracing = "0.1.13"
tracing-subscriber = "0.2.4"
near-indexer = { git = "https://github.com/near/nearcore", rev = "25b000ae4dd9fe784695d07a3f2e99d82a6f10bd" }
- Update
edition
to2021
- Drop
actix
crates - Drop
openssl-probe
crate - Add
futures
anditertools
- Add features to
tokio
as we will be using tokio runtime - Add
tokio-stream
crate - Replace
near-indexer
withnear-lake-framework
So in the end we'll have this after all:
[package]
name = "indexer-tx-watcher-example"
version = "0.1.0"
authors = ["Near Inc <hello@nearprotocol.com>"]
edition = "2021"
[dependencies]
base64 = "0.11"
clap = { version = "3.1.6", features = ["derive"] }
futures = "0.3.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.55"
itertools = "0.9.0"
tokio = { version = "1.1", features = ["sync", "time", "macros", "rt-multi-thread"] }
tokio-stream = { version = "0.1" }
tracing = "0.1.13"
tracing-subscriber = "0.2.4"
near-lake-framework = "0.4.0"
Change the clap configs
Currently we have structure Opts
that has a subcommand with Run
and Init
command. Since NEAR Lake Framework doesn't need data
and config files we don't need Init
at all. So we need to combine some structures into Opts
itself.
...
/// NEAR Indexer Example
/// Watches for stream of blocks from the chain
#[derive(Clap, Debug)]
#[clap(version = "0.1", author = "Near Inc. <hello@nearprotocol.com>")]
pub(crate) struct Opts {
/// Sets a custom config dir. Defaults to ~/.near/
#[clap(short, long)]
pub home_dir: Option<std::path::PathBuf>,
#[clap(subcommand)]
pub subcmd: SubCommand,
}
#[derive(Clap, Debug)]
pub(crate) enum SubCommand {
/// Run NEAR Indexer Example. Start observe the network
Run(RunArgs),
/// Initialize necessary configs
Init(InitConfigArgs),
}
#[derive(Clap, Debug)]
pub(crate) struct RunArgs {
/// account ids to watch for
#[clap(long)]
pub accounts: String,
}
#[derive(Clap, Debug)]
pub(crate) struct InitConfigArgs {
...
}
...
We are going:
- Drop
InitConfigArgs
completely - Move the content from
RunArgs
toOpts
and then dropRunArgs
- Drop
home_dir
fromOpts
- Add
block_height
toOpts
to know from which block height to start indexing - Refactor
SubCommand
to have to variants: mainnet and testnet to define what chain to index - And add
Clone
detive to the structs for later
/// NEAR Indexer Example
/// Watches for stream of blocks from the chain
#[derive(Clap, Debug, Clone)]
#[clap(version = "0.1", author = "Near Inc. <hello@nearprotocol.com>")]
pub(crate) struct Opts {
/// block height to start indexing from
#[clap(long)]
pub block_height: u64,
/// account ids to watch for
#[clap(long)]
pub accounts: String,
#[clap(subcommand)]
pub subcmd: SubCommand,
}
#[derive(Clap, Debug, Clone)]
pub(crate) enum SubCommand {
Mainnet,
Testnet,
}
In the end of the file we have one implementation we need to replace.
...
impl From<InitConfigArgs> for near_indexer::InitConfigArgs {
...
}
We want to be able to cast Opts
to near_lake_framework::LakeConfig
. So we're going to create a new implementation.
impl From<Opts> for near_lake_framework::LakeConfig {
fn from(opts: Opts) -> Self {
let mut lake_config =
near_lake_framework::LakeConfigBuilder::default().start_block_height(opts.block_height);
match &opts.subcmd {
SubCommand::Mainnet => {
lake_config = lake_config.mainnet();
}
SubCommand::Testnet => {
lake_config = lake_config.testnet();
}
};
lake_config.build().expect("Failed to build LakeConfig")
}
}
And the final move is to change init_logging
function to remove redundant log subscriptions:
...
pub(crate) fn init_logging() {
let env_filter = EnvFilter::new(
"tokio_reactor=info,near=info,stats=info,telemetry=info,indexer_example=info,indexer=info,near-performance-metrics=info",
);
tracing_subscriber::fmt::Subscriber::builder()
.with_env_filter(env_filter)
.with_writer(std::io::stderr)
.init();
}
...
Replace it with
...
pub(crate) fn init_logging() {
let env_filter = EnvFilter::new("near_lake_framework=info");
tracing_subscriber::fmt::Subscriber::builder()
.with_env_filter(env_filter)
.with_writer(std::io::stderr)
.init();
}
...
Finally we're done with src/config.rs
and now we can move on to src/main.rs
Replacing the indexer instantiation
Since we can use tokio
runtime and make our main
function asynchronous it's shorted to show the recreating of the main
function than the process of refactoring.
Let's start from import section
Imports before
use std::str::FromStr;
use std::collections::{HashMap, HashSet};
use clap::Clap;
use tokio::sync::mpsc;
use tracing::info;
use configs::{init_logging, Opts, SubCommand};
mod configs;
Imports after
We're adding near_lake_framework
imports and remove redundant import from configs
.
use std::str::FromStr;
use std::collections::{HashMap, HashSet};
use clap::Clap;
use tokio::sync::mpsc;
use tracing::info;
use near_lake_framework::near_indexer_primitives;
use near_lake_framework::LakeConfig;
use configs::{init_logging, Opts};
Creating main()
Let's create an async main()
function, call init_logging
and read the Opts
.
#[tokio::main]
async fn main() -> Result<(), tokio::io::Error> {
init_logging();
let opts: Opts = Opts::parse();
Let's cast LakeConfig
from Opts
and instantiate NEAR Lake Framework's stream
#[tokio::main]
async fn main() -> Result<(), tokio::io::Error> {
init_logging();
let opts: Opts = Opts::parse();
let config: LakeConfig = opts.clone().into();
let (_, stream) = near_lake_framework::streamer(config);
Copy/paste the code of reading accounts
arg to Vec<AccountId
> from the old main()
#[tokio::main]
async fn main() -> Result<(), tokio::io::Error> {
init_logging();
let opts: Opts = Opts::parse();
let config: LakeConfig = opts.clone().into();
let (_, stream) = near_lake_framework::streamer(config);
let watching_list = opts
.accounts
.split(',')
.map(|elem| {
near_indexer_primitives::types::AccountId::from_str(elem).expect("AccountId is invalid")
})
.collect();
Now we can call listen_blocks
function we have used before in our indexer while it was built on top of NEAR Indexer Framework. And return Ok(())
so our main()
would be happy.
Final async main with NEAR Lake Framework stream
#[tokio::main]
async fn main() -> Result<(), tokio::io::Error> {
init_logging();
let opts: Opts = Opts::parse();
let config: LakeConfig = opts.clone().into();
let (_, stream) = near_lake_framework::streamer(config);
let watching_list = opts
.accounts
.split(',')
.map(|elem| {
near_indexer_primitives::types::AccountId::from_str(elem).expect("AccountId is invalid")
})
.collect();
listen_blocks(stream, watching_list).await;
Ok(())
}