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

chain: don't skip early transaction validity checks in state witnesses #12627

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
84 changes: 40 additions & 44 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ pub struct Chain {
pub(crate) orphans: OrphanBlockPool,
pub blocks_with_missing_chunks: MissingChunksPool<Orphan>,
genesis: Block,
pub transaction_validity_period: NumBlocks,
pub epoch_length: BlockHeightDelta,
/// Block economics, relevant to changes when new block must be produced.
pub block_economics_config: BlockEconomicsConfig,
Expand Down Expand Up @@ -365,7 +364,13 @@ impl Chain {
save_trie_changes: bool,
) -> Result<Chain, Error> {
let store = runtime_adapter.store();
let chain_store = ChainStore::new(store.clone(), chain_genesis.height, save_trie_changes);
let transaction_validity_period = chain_genesis.transaction_validity_period;
let chain_store = ChainStore::new(
store.clone(),
chain_genesis.height,
save_trie_changes,
transaction_validity_period,
);
let state_roots = get_genesis_state_roots(runtime_adapter.store())?
.expect("genesis should be initialized.");
let (genesis, _genesis_chunks) = Self::make_genesis_block(
Expand All @@ -392,7 +397,6 @@ impl Chain {
blocks_with_missing_chunks: MissingChunksPool::new(),
blocks_in_processing: BlocksInProcessing::new(),
genesis,
transaction_validity_period: chain_genesis.transaction_validity_period,
epoch_length: chain_genesis.epoch_length,
block_economics_config: BlockEconomicsConfig::from(chain_genesis),
doomslug_threshold_mode,
Expand Down Expand Up @@ -430,12 +434,14 @@ impl Chain {
chain_genesis,
state_roots.clone(),
)?;
let transaction_validity_period = chain_genesis.transaction_validity_period;

// Check if we have a head in the store, otherwise pick genesis block.
let mut chain_store = ChainStore::new(
runtime_adapter.store().clone(),
chain_genesis.height,
chain_config.save_trie_changes,
transaction_validity_period,
);
let mut store_update = chain_store.store_update();
let (block_head, header_head) = match store_update.head() {
Expand Down Expand Up @@ -580,7 +586,6 @@ impl Chain {
blocks_in_processing: BlocksInProcessing::new(),
invalid_blocks: LruCache::new(NonZeroUsize::new(INVALID_CHUNKS_POOL_SIZE).unwrap()),
genesis: genesis.clone(),
transaction_validity_period: chain_genesis.transaction_validity_period,
epoch_length: chain_genesis.epoch_length,
block_economics_config: BlockEconomicsConfig::from(chain_genesis),
doomslug_threshold_mode,
Expand Down Expand Up @@ -1546,7 +1551,6 @@ impl Chain {
self.epoch_manager.clone(),
self.runtime_adapter.clone(),
self.doomslug_threshold_mode,
self.transaction_validity_period,
)
}

Expand Down Expand Up @@ -3170,48 +3174,34 @@ impl Chain {
block: &Block,
prev_block_header: &BlockHeader,
chunk: &ShardChunk,
) -> Result<(), Error> {
) -> Result<Vec<bool>, Error> {
let protocol_version =
self.epoch_manager.get_epoch_protocol_version(block.header().epoch_id())?;

if checked_feature!(
let relaxed_chunk_validation = checked_feature!(
"protocol_feature_relaxed_chunk_validation",
RelaxedChunkValidation,
protocol_version
) {
return Ok(());
}
);

if !validate_transactions_order(chunk.transactions()) {
let merkle_paths =
Block::compute_chunk_headers_root(block.chunks().iter_deprecated()).1;
let epoch_id = block.header().epoch_id();
let shard_layout = self.epoch_manager.get_shard_layout(&epoch_id)?;
let shard_id = chunk.shard_id();
let shard_index = shard_layout.get_shard_index(shard_id)?;

let chunk_proof = ChunkProofs {
block_header: borsh::to_vec(&block.header()).expect("Failed to serialize"),
merkle_proof: merkle_paths[shard_index].clone(),
chunk: MaybeEncodedShardChunk::Decoded(chunk.clone()).into(),
};
return Err(Error::InvalidChunkProofs(Box::new(chunk_proof)));
}
if !relaxed_chunk_validation {
if !validate_transactions_order(chunk.transactions()) {
let merkle_paths =
Block::compute_chunk_headers_root(block.chunks().iter_deprecated()).1;
let epoch_id = block.header().epoch_id();
let shard_layout = self.epoch_manager.get_shard_layout(&epoch_id)?;
let shard_id = chunk.shard_id();
let shard_index = shard_layout.get_shard_index(shard_id)?;

if checked_feature!("stable", AccessKeyNonceRange, protocol_version) {
let transaction_validity_period = self.transaction_validity_period;
for transaction in chunk.transactions() {
self.chain_store()
.check_transaction_validity_period(
prev_block_header,
transaction.transaction.block_hash(),
transaction_validity_period,
)
.map_err(|_| Error::from(Error::InvalidTransactions))?;
let chunk_proof = ChunkProofs {
block_header: borsh::to_vec(&block.header()).expect("Failed to serialize"),
merkle_proof: merkle_paths[shard_index].clone(),
chunk: MaybeEncodedShardChunk::Decoded(chunk.clone()).into(),
};
return Err(Error::InvalidChunkProofs(Box::new(chunk_proof)));
}
};
}

Ok(())
self.chain_store().compute_transaction_validity(protocol_version, prev_block_header, chunk)
}

pub fn transaction_validity_check<'a>(
Expand All @@ -3220,11 +3210,7 @@ impl Chain {
) -> impl FnMut(&SignedTransaction) -> bool + 'a {
move |tx: &SignedTransaction| -> bool {
self.chain_store()
.check_transaction_validity_period(
&prev_block_header,
tx.transaction.block_hash(),
self.transaction_validity_period,
)
.check_transaction_validity_period(&prev_block_header, tx.transaction.block_hash())
.is_ok()
}
}
Expand Down Expand Up @@ -3773,7 +3759,8 @@ impl Chain {
}
})?;

self.validate_chunk_transactions(&block, prev_block.header(), &chunk)?;
let tx_valid_list =
self.validate_chunk_transactions(&block, prev_block.header(), &chunk)?;

// we can't use hash from the current block here yet because the incoming receipts
// for this block is not stored yet
Expand Down Expand Up @@ -3806,6 +3793,7 @@ impl Chain {
ShardUpdateReason::NewChunk(NewChunkData {
chunk_header: chunk_header.clone(),
transactions: chunk.transactions().to_vec(),
transaction_validity_check_results: tx_valid_list,
receipts,
block: block_context,
is_first_block_with_chunk_of_version,
Expand Down Expand Up @@ -3971,6 +3959,14 @@ impl Chain {
}
}
}

pub fn transaction_validity_period(&self) -> BlockHeightDelta {
self.chain_store.transaction_validity_period
}

pub fn set_transaction_validity_period(&mut self, to: BlockHeightDelta) {
self.chain_store.transaction_validity_period = to;
}
}

/// This method calculates the congestion info for the genesis chunks. It uses
Expand Down
58 changes: 29 additions & 29 deletions chain/chain/src/chain_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ use near_primitives::shard_layout::ShardUId;
use near_primitives::sharding::ShardChunk;
use near_primitives::state_sync::{ReceiptProofResponse, ShardStateSyncResponseHeader};
use near_primitives::types::chunk_extra::ChunkExtra;
use near_primitives::types::{BlockExtra, BlockHeight, BlockHeightDelta, ShardId};
use near_primitives::types::{BlockExtra, BlockHeight, ShardId};
use near_primitives::views::LightClientBlockView;
use node_runtime::SignedValidPeriodTransactions;
use std::sync::Arc;
use tracing::{debug, info, warn};

Expand All @@ -35,8 +36,6 @@ pub struct ChainUpdate<'a> {
runtime_adapter: Arc<dyn RuntimeAdapter>,
chain_store_update: ChainStoreUpdate<'a>,
doomslug_threshold_mode: DoomslugThresholdMode,
#[allow(unused)]
transaction_validity_period: BlockHeightDelta,
}

impl<'a> ChainUpdate<'a> {
Expand All @@ -45,32 +44,18 @@ impl<'a> ChainUpdate<'a> {
epoch_manager: Arc<dyn EpochManagerAdapter>,
runtime_adapter: Arc<dyn RuntimeAdapter>,
doomslug_threshold_mode: DoomslugThresholdMode,
transaction_validity_period: BlockHeightDelta,
) -> Self {
let chain_store_update: ChainStoreUpdate<'_> = chain_store.store_update();
Self::new_impl(
epoch_manager,
runtime_adapter,
doomslug_threshold_mode,
transaction_validity_period,
chain_store_update,
)
Self::new_impl(epoch_manager, runtime_adapter, doomslug_threshold_mode, chain_store_update)
}

fn new_impl(
epoch_manager: Arc<dyn EpochManagerAdapter>,
runtime_adapter: Arc<dyn RuntimeAdapter>,
doomslug_threshold_mode: DoomslugThresholdMode,
transaction_validity_period: BlockHeightDelta,
chain_store_update: ChainStoreUpdate<'a>,
) -> Self {
ChainUpdate {
epoch_manager,
runtime_adapter,
chain_store_update,
doomslug_threshold_mode,
transaction_validity_period,
}
ChainUpdate { epoch_manager, runtime_adapter, chain_store_update, doomslug_threshold_mode }
}

/// Commit changes to the chain into the database.
Expand Down Expand Up @@ -513,11 +498,17 @@ impl<'a> ChainUpdate<'a> {
}
}
let receipts = collect_receipts_from_response(&receipt_proof_responses);
// Prev block header should be present during state sync, since headers have been synced at this point.
let gas_price = if block_header.height() == self.chain_store_update.get_genesis_height() {
block_header.next_gas_price()
let is_genesis = block_header.height() == self.chain_store_update.get_genesis_height();
let prev_block_header = is_genesis
.then(|| self.chain_store_update.get_block_header(block_header.prev_hash()))
.transpose()?;

// Prev block header should be present during state sync, since headers have been synced at
// this point, except for genesis.
let gas_price = if let Some(prev_block_header) = &prev_block_header {
prev_block_header.next_gas_price()
} else {
self.chain_store_update.get_block_header(block_header.prev_hash())?.next_gas_price()
block_header.next_gas_price()
};

let chunk_header = chunk.cloned_header();
Expand All @@ -528,7 +519,19 @@ impl<'a> ChainUpdate<'a> {
let is_first_block_with_chunk_of_version = false;

let block = self.chain_store_update.get_block(block_header.hash())?;

let epoch_id = self.epoch_manager.get_epoch_id(block_header.hash())?;
let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id)?;
let transactions = chunk.transactions();
let transaction_validity = if let Some(prev_block_header) = prev_block_header {
self.chain_store_update.chain_store().compute_transaction_validity(
protocol_version,
&prev_block_header,
&chunk,
)?
} else {
vec![true; transactions.len()]
};
let transactions = SignedValidPeriodTransactions::new(transactions, &transaction_validity);
let apply_result = self.runtime_adapter.apply_chunk(
RuntimeStorageConfig::new(chunk_header.prev_state_root(), true),
ApplyChunkReason::UpdateTrackedShard,
Expand All @@ -551,7 +554,7 @@ impl<'a> ChainUpdate<'a> {
bandwidth_requests: block.block_bandwidth_requests(),
},
&receipts,
chunk.transactions(),
transactions,
)?;

let (outcome_root, outcome_proofs) =
Expand All @@ -572,9 +575,6 @@ impl<'a> ChainUpdate<'a> {

self.chain_store_update.save_trie_changes(apply_result.trie_changes);

let epoch_id = self.epoch_manager.get_epoch_id(block_header.hash())?;
let protocol_version = self.epoch_manager.get_epoch_protocol_version(&epoch_id)?;

let chunk_extra = ChunkExtra::new(
protocol_version,
&apply_result.new_root,
Expand Down Expand Up @@ -659,7 +659,7 @@ impl<'a> ChainUpdate<'a> {
block.block_bandwidth_requests(),
),
&[],
&[],
SignedValidPeriodTransactions::new(&[], &[]),
)?;
let flat_storage_manager = self.runtime_adapter.get_flat_storage_manager();
let store_update = flat_storage_manager.save_flat_state_changes(
Expand Down
11 changes: 8 additions & 3 deletions chain/chain/src/flat_storage_resharder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use std::iter;
/// - Children shard catchup can be cancelled and will resume from the point where it left.
/// - Resilience to chain forks.
/// - Resharding events will perform changes on the state only after their resharding block
/// becomes final.
/// becomes final.
#[derive(Clone)]
pub struct FlatStorageResharder {
runtime: Arc<dyn RuntimeAdapter>,
Expand Down Expand Up @@ -1353,7 +1353,12 @@ mod tests {
epoch_manager.clone(),
);
let chain_genesis = ChainGenesis::new(&genesis.config);
let sender = Arc::new(T::new(ChainStore::new(store, chain_genesis.height, false)));
let sender = Arc::new(T::new(ChainStore::new(
store,
chain_genesis.height,
false,
chain_genesis.transaction_validity_period,
)));
let chain = Chain::new(
Clock::real(),
epoch_manager,
Expand Down Expand Up @@ -2370,7 +2375,7 @@ mod tests {
assert_eq!(sender.memtrie_reload_requests(), vec![left_child_shard, right_child_shard]);
}

/// Creates a new account through a state change saved as flat storage deltas.
/// Creates a new account through a state change saved as flat storage deltas.
fn create_new_account_through_deltas(
manager: &FlatStorageManager,
account: AccountId,
Expand Down
14 changes: 10 additions & 4 deletions chain/chain/src/resharding/resharding_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ use super::types::{
FlatStorageShardCatchupRequest, FlatStorageSplitShardRequest, MemtrieReloadRequest,
};
use crate::flat_storage_resharder::{FlatStorageResharder, FlatStorageReshardingTaskResult};
use crate::ChainStore;
use crate::{ChainGenesis, ChainStore};
use near_async::futures::{DelayedActionRunner, DelayedActionRunnerExt};
use near_async::messaging::{self, Handler, HandlerWithContext};
use near_primitives::shard_layout::ShardUId;
use near_primitives::types::BlockHeight;
use near_store::Store;
use time::Duration;

Expand Down Expand Up @@ -44,8 +43,15 @@ impl Handler<MemtrieReloadRequest> for ReshardingActor {
}

impl ReshardingActor {
pub fn new(store: Store, genesis_height: BlockHeight) -> Self {
Self { chain_store: ChainStore::new(store, genesis_height, false) }
pub fn new(store: Store, genesis: &ChainGenesis) -> Self {
Self {
chain_store: ChainStore::new(
store,
genesis.height,
false,
genesis.transaction_validity_period,
),
}
}

fn handle_flat_storage_split_shard(
Expand Down
4 changes: 2 additions & 2 deletions chain/chain/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl NightshadeRuntime {
chunk: ApplyChunkShardContext,
block: ApplyChunkBlockContext,
receipts: &[Receipt],
transactions: &[SignedTransaction],
transactions: node_runtime::SignedValidPeriodTransactions<'_>,
state_patch: SandboxStatePatch,
) -> Result<ApplyChunkResult, Error> {
let ApplyChunkBlockContext {
Expand Down Expand Up @@ -838,7 +838,7 @@ impl RuntimeAdapter for NightshadeRuntime {
chunk: ApplyChunkShardContext,
block: ApplyChunkBlockContext,
receipts: &[Receipt],
transactions: &[SignedTransaction],
transactions: node_runtime::SignedValidPeriodTransactions<'_>,
) -> Result<ApplyChunkResult, Error> {
let shard_id = chunk.shard_id;
let _timer = metrics::APPLYING_CHUNKS_TIME
Expand Down
Loading
Loading