Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add from-genesis subcommand #342

Merged
merged 19 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0fe4083
feat: Add access_key/account adapters for storing genesis
morgsmccauley Feb 1, 2023
71e83d6
feat: Add `from-genesis` command without implementation
morgsmccauley Feb 1, 2023
c5917f3
feat: Download genesis file
morgsmccauley Feb 1, 2023
1da6193
feat: Store genesis accounts/access_keys
morgsmccauley Feb 1, 2023
334b6ad
refactor: Initialize lake framework after processing genesis
morgsmccauley Feb 1, 2023
93b057d
docs: Add info on `from-genesis` to `README.md`
morgsmccauley Feb 1, 2023
5f2fcaa
feat: Add `betanet` s3 genesis url
morgsmccauley Mar 22, 2023
b4c6b87
refactor: Replace manual chain id conversion with `impl ToString`
morgsmccauley Mar 22, 2023
10fbdba
feat: Show `genesis.json` download progress in logs
morgsmccauley Mar 22, 2023
a3aabdf
feat: Use `genesis.json` on disk if exists
morgsmccauley Mar 22, 2023
9194d47
chore: Remove unused `actix-rt`
morgsmccauley Mar 23, 2023
8864299
chore: Remove unused imports
morgsmccauley Mar 24, 2023
3987f74
refactor: Stream genesis record processing
morgsmccauley Mar 24, 2023
a749872
doc: Add note in `README.md` about RAM requirements for testnet genesis
morgsmccauley Mar 27, 2023
a5fcc9f
fix: Set `target` for genesis logs
morgsmccauley Mar 27, 2023
2976446
refactor: Remove genesis chain ID file prefix
morgsmccauley Mar 27, 2023
6537660
refactor: Store `genesis.json` next to executable
morgsmccauley Mar 27, 2023
45df278
refactor: Log genesis download every ~100kb
morgsmccauley Mar 27, 2023
8f00af8
doc: Update `CHANGELOG.md`
morgsmccauley Mar 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,9 @@ Command to run NEAR Indexer for Explorer have to include the chain-id and start
You can choose NEAR Indexer for Explorer start options:
- `from-latest` - start indexing blocks from the latest finalized block
- `from-interruption` - start indexing blocks from the block NEAR Indexer was interrupted last time but earlier for `<number_of_blocks>` if provided
- `from-genesis` - download and store accounts/access keys in genesis file and start indexing from the genesis block
- `from-block --height <block_height>` - start indexing blocks from the specific block height

#### Storing genesis file
Unlike the original NEAR Indexer for Explorer you **can't** tell Indexer to store data from genesis (Accounts and Access Keys) by adding key `--store-genesis` to the `run` command. So please, ensure you took care about the genesis data in your database in order this indexer to work properly. This capability will be implemented eventually, it's progress can be tracked here: #327.

#### Strict mode
NEAR Indexer for Explorer works in strict mode by default. In strict mode, the Indexer will ensure parent data exists before storing children, infinitely retrying until this condition is met. This is necessary as a parent (i.e. `block`) may still be processing while a child (i.e. `receipt`) is ready to be stored. This scenario will likely occur if you have not stored the genesis file or do not have all data prior to the block you start indexing from. In this case, you can disable strict mode to store data prior to the block you are concerned about, and then re-enable it once you have passed this block.

Expand Down
1 change: 1 addition & 0 deletions database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ actix-diesel = { git = "https://github.com/frol/actix-diesel", rev = "3a001986c8
near-indexer-primitives = "0.16.0"
near-primitives = "0.16.0"
near-crypto = "0.16.0"
near-chain-configs = "0.16.0"

[features]
default = []
Expand Down
24 changes: 24 additions & 0 deletions database/src/adapters/access_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use actix_diesel::dsl::AsyncRunQueryDsl;

use diesel::{ExpressionMethods, PgConnection, QueryDsl};
use futures::try_join;
use tracing::info;

use crate::models;
use crate::schema;
Expand Down Expand Up @@ -145,3 +146,26 @@ pub async fn handle_access_keys(

Ok(())
}

pub(crate) async fn store_access_keys_from_genesis(
pool: &actix_diesel::Database<PgConnection>,
access_keys_models: Vec<models::access_keys::AccessKey>,
) -> anyhow::Result<()> {
info!(
target: crate::EXPLORER_DATABASE,
"Adding/updating {} access keys from genesis...",
access_keys_models.len(),
);

crate::await_retry_or_panic!(
diesel::insert_into(schema::access_keys::table)
.values(access_keys_models.clone())
.on_conflict_do_nothing()
.execute_async(pool),
10,
"Failed to store AccessKeys from genesis".to_string(),
&access_keys_models
);

Ok(())
}
24 changes: 24 additions & 0 deletions database/src/adapters/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use bigdecimal::BigDecimal;

use diesel::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl};
use futures::try_join;
use tracing::info;

use crate::models;
use crate::schema;
Expand Down Expand Up @@ -249,3 +250,26 @@ pub async fn get_lockup_account_ids_at_block_height(
.collect()
})
}

pub(crate) async fn store_accounts_from_genesis(
pool: &actix_diesel::Database<PgConnection>,
accounts_models: Vec<models::accounts::Account>,
) -> anyhow::Result<()> {
info!(
target: crate::EXPLORER_DATABASE,
"Adding/updating {} accounts from genesis...",
accounts_models.len(),
);

crate::await_retry_or_panic!(
diesel::insert_into(schema::accounts::table)
.values(accounts_models.clone())
.on_conflict_do_nothing()
.execute_async(pool),
10,
"Failed to store Accounts from genesis".to_string(),
&accounts_models
);

Ok(())
}
59 changes: 59 additions & 0 deletions database/src/adapters/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use actix_diesel::Database;
khorolets marked this conversation as resolved.
Show resolved Hide resolved
use diesel::PgConnection;
use near_chain_configs::{Genesis, GenesisValidationMode};

use crate::adapters::access_keys::store_access_keys_from_genesis;
use crate::adapters::accounts::store_accounts_from_genesis;

pub async fn store_genesis_records(
pool: &Database<PgConnection>,
genesis_file_path: String,
) -> anyhow::Result<()> {
let mut accounts_to_store: Vec<crate::models::accounts::Account> = vec![];
let mut access_keys_to_store: Vec<crate::models::access_keys::AccessKey> = vec![];

let genesis = Genesis::from_file(genesis_file_path, GenesisValidationMode::Full);

let genesis_height = genesis.config.genesis_height;

for record in genesis.records.0 {
if accounts_to_store.len() == 5_000 {
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
let mut accounts_to_store_chunk = vec![];
std::mem::swap(&mut accounts_to_store, &mut accounts_to_store_chunk);
store_accounts_from_genesis(pool, accounts_to_store_chunk).await?;
}

if access_keys_to_store.len() == 5_000 {
let mut access_keys_to_store_chunk = vec![];
std::mem::swap(&mut access_keys_to_store, &mut access_keys_to_store_chunk);
store_access_keys_from_genesis(pool, access_keys_to_store_chunk).await?;
}

match record {
near_primitives::state_record::StateRecord::Account { account_id, .. } => {
accounts_to_store.push(crate::models::accounts::Account::new_from_genesis(
&account_id,
genesis_height,
));
}
near_primitives::state_record::StateRecord::AccessKey {
account_id,
public_key,
access_key,
} => {
access_keys_to_store.push(crate::models::access_keys::AccessKey::from_genesis(
&public_key,
&account_id,
&access_key,
genesis_height,
));
}
_ => {}
};
}

store_accounts_from_genesis(pool, accounts_to_store).await?;
store_access_keys_from_genesis(pool, access_keys_to_store).await?;
telezhnaya marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}
1 change: 1 addition & 0 deletions database/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod assets;
pub mod blocks;
pub mod chunks;
pub mod execution_outcomes;
pub mod genesis;
pub mod receipts;
pub mod transactions;

Expand Down
1 change: 1 addition & 0 deletions indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ lazy_static = "^1.4"
near-sdk = { git = "https://github.com/near/near-sdk-rs", rev="03487c184d37b0382dd9bd41c57466acad58fc1f" }
openssl-probe = { version = "0.1.2" }
prometheus = "0.13.0"
reqwest = "0.11.14"
r2d2 = "0.8.8"
tokio = { version = "1.1", features = ["sync", "time"] }
tokio-stream = { version = "0.1" }
Expand Down
14 changes: 13 additions & 1 deletion indexer/src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@ pub enum ChainId {
}

#[allow(clippy::enum_variant_names)]
#[derive(Subcommand, Debug, Clone)]
#[derive(Subcommand, Debug, Clone, PartialEq, Eq)]
pub enum StartOptions {
/// Start from specific block height
FromBlock { height: u64 },
/// Start from interruption (last_indexed_block value from Redis)
FromInterruption,
/// Start from the final block on the network (queries JSON RPC for finality: final)
FromLatest,
/// Store genesis data and start from first block
FromGenesis,
}

impl Opts {
Expand All @@ -77,6 +79,13 @@ impl Opts {
ChainId::Betanet(_) => "https://rpc.betanet.near.org",
}
}

pub fn genesis_file_url(&self) -> &str {
match self.chain_id {
ChainId::Mainnet(_) => "https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/mainnet/genesis.json",
ChainId::Testnet(_) => "https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/testnet/genesis.json",
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl Opts {
Expand Down Expand Up @@ -114,6 +123,9 @@ async fn get_start_block_height(opts: &Opts) -> u64 {
}
}
StartOptions::FromLatest => final_block_height(opts).await,
// Since NEAR Lake stores blocks in ascending order, using 0 here forces
// near-lake-framework to start from the first block, i.e. genesis
StartOptions::FromGenesis => 0,
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
40 changes: 34 additions & 6 deletions indexer/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io::Write;

use clap::Parser;

pub use cached::SizedCache;
Expand All @@ -8,7 +10,7 @@ use tracing::{debug, info};

use explorer_database::{adapters, models, receipts_cache};

use crate::configs::Opts;
use crate::configs::{ChainId, Opts, StartOptions};

mod configs;
mod metrics;
Expand Down Expand Up @@ -139,6 +141,27 @@ async fn handle_message(
Ok(())
}

async fn download_genesis_file(opts: &configs::Opts) -> anyhow::Result<String> {
let chain = match opts.chain_id {
ChainId::Mainnet(_) => "mainnet",
ChainId::Testnet(_) => "testnet",
};

info!(
target: INDEXER_FOR_EXPLORER,
"Downloading {} genesis file", chain
);

let resp = reqwest::get(opts.genesis_file_url()).await?;

let file_path = format!("{}-genesis.json", chain);
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved

let mut file = std::fs::File::create(&file_path)?;
file.write_all(&resp.bytes().await?)?;

Ok(file_path.to_string())
}

#[actix::main]
async fn main() -> anyhow::Result<()> {
// We use it to automatically search the for root certificates to perform HTTPS calls
Expand All @@ -165,16 +188,21 @@ async fn main() -> anyhow::Result<()> {
let receipts_cache_arc: receipts_cache::ReceiptsCacheArc =
std::sync::Arc::new(Mutex::new(SizedCache::with_size(100_000)));

let config: near_lake_framework::LakeConfig = opts.to_lake_config().await;
let (sender, stream) = near_lake_framework::streamer(config);

tokio::spawn(metrics::init_server(opts.port).expect("Failed to start metrics server"));

tracing::info!(
target: INDEXER_FOR_EXPLORER,
"Starting Indexer for Explorer (lake)...",
);

tokio::spawn(metrics::init_server(opts.port).expect("Failed to start metrics server"));

if opts.start_options() == &StartOptions::FromGenesis {
let genesis_file_path = download_genesis_file(&opts).await?;
adapters::genesis::store_genesis_records(&pool, genesis_file_path).await?;
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
}

let config: near_lake_framework::LakeConfig = opts.to_lake_config().await;
let (sender, stream) = near_lake_framework::streamer(config);

let mut handlers = tokio_stream::wrappers::ReceiverStream::new(stream)
.map(|streamer_message| {
info!(
Expand Down