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

reconstruct balances from blocks #3506

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
46 changes: 27 additions & 19 deletions rs/ledger_suite/common/ledger_canister_core/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ where
L: LedgerData,
L::BalancesStore: InspectableBalancesStore,
{
let result = apply_transaction_no_trimming(ledger, transaction, now, effective_fee);
let result = apply_transaction_no_trimming(ledger, transaction, now, effective_fee, None);
trim_balances(ledger, now);
result
}
Expand All @@ -231,6 +231,7 @@ pub fn apply_transaction_no_trimming<L>(
transaction: L::Transaction,
now: TimeStamp,
effective_fee: L::Tokens,
block_index: Option<u64>,
) -> Result<(BlockIndex, HashOf<EncodedBlock>), TransferError<L::Tokens>>
where
L: LedgerData,
Expand All @@ -249,22 +250,24 @@ where
.created_at_time()
.map(|created_at_time| (created_at_time, transaction.hash()));

if let Some((created_at_time, tx_hash)) = maybe_time_and_hash {
// The caller requested deduplication.
if created_at_time + ledger.transaction_window() < now {
return Err(TransferError::TxTooOld {
allowed_window_nanos: ledger.transaction_window().as_nanos() as u64,
});
}
if block_index.is_none() {
if let Some((created_at_time, tx_hash)) = maybe_time_and_hash {
// The caller requested deduplication.
if created_at_time + ledger.transaction_window() < now {
return Err(TransferError::TxTooOld {
allowed_window_nanos: ledger.transaction_window().as_nanos() as u64,
});
}

if created_at_time > now + ic_limits::PERMITTED_DRIFT {
return Err(TransferError::TxCreatedInFuture { ledger_time: now });
}
if created_at_time > now + ic_limits::PERMITTED_DRIFT {
return Err(TransferError::TxCreatedInFuture { ledger_time: now });
}

if let Some(block_height) = ledger.transactions_by_hash().get(&tx_hash) {
return Err(TransferError::TxDuplicate {
duplicate_of: *block_height,
});
if let Some(block_height) = ledger.transactions_by_hash().get(&tx_hash) {
return Err(TransferError::TxDuplicate {
duplicate_of: *block_height,
});
}
}
}

Expand Down Expand Up @@ -296,10 +299,15 @@ where
);
let block_timestamp = block.timestamp();

let height = ledger
.blockchain_mut()
.add_block(block)
.expect("failed to add block");
let height = if let Some(block_index) = block_index {
block_index
} else {
ledger
.blockchain_mut()
.add_block(block)
.expect("failed to add block")
};

if let Some(fee_collector) = ledger.fee_collector_mut().as_mut() {
if fee_collector.block_index.is_none() {
fee_collector.block_index = Some(height);
Expand Down
52 changes: 45 additions & 7 deletions rs/ledger_suite/icrc1/ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ pub enum LedgerField {
pub enum LedgerState {
Migrating(LedgerField),
Ready,
ReadyReadOnly,
}

impl Default for LedgerState {
Expand Down Expand Up @@ -673,14 +674,13 @@ impl Ledger {
)
});
let mint = Transaction::mint(account, amount, Some(now), None);
apply_transaction_no_trimming(&mut ledger, mint, now, Tokens::ZERO).unwrap_or_else(
|err| {
apply_transaction_no_trimming(&mut ledger, mint, now, Tokens::ZERO, None)
.unwrap_or_else(|err| {
panic!(
"failed to mint {} tokens to {}: {:?}",
balance, account, err
)
},
);
});
}

ledger
Expand Down Expand Up @@ -742,6 +742,7 @@ impl LedgerContext for Ledger {

fn balances_mut(&mut self) -> &mut Balances<Self::BalancesStore> {
panic_if_not_ready();
panic_if_read_only();
&mut self.stable_balances
}

Expand All @@ -752,6 +753,7 @@ impl LedgerContext for Ledger {

fn approvals_mut(&mut self) -> &mut AllowanceTable<Self::AllowancesData> {
panic_if_not_ready();
panic_if_read_only();
&mut self.stable_approvals
}

Expand Down Expand Up @@ -971,11 +973,14 @@ impl Ledger {
length: usize,
decode: impl Fn(&EncodedBlock) -> B,
make_callback: impl Fn(Principal) -> ArchiveFn,
max_transactions: Option<usize>,
) -> (u64, Vec<B>, Vec<ArchivedRange<ArchiveFn>>) {
let locations = block_locations(self, start, length);

let local_blocks_range =
range_utils::take(&locations.local_blocks, MAX_TRANSACTIONS_PER_REQUEST);
let local_blocks_range = range_utils::take(
&locations.local_blocks,
max_transactions.unwrap_or(MAX_TRANSACTIONS_PER_REQUEST),
);

let local_blocks: Vec<B> = self
.blockchain
Expand Down Expand Up @@ -1008,6 +1013,7 @@ impl Ledger {
decoded_block.into()
},
|canister_id| QueryTxArchiveFn::new(canister_id, "get_transactions"),
None,
);

GetTransactionsResponse {
Expand All @@ -1018,13 +1024,31 @@ impl Ledger {
}
}

/// Returns local ledger blocks.
pub fn get_ledger_blocks(&self, start: BlockIndex, length: usize) -> Vec<Block<Tokens>> {
let (first_index, local_blocks, archived_transactions) = self.query_blocks(
start,
length,
|enc_block| -> Block<Tokens> {
Block::decode(enc_block.clone()).expect("bug: failed to decode encoded block")
},
|canister_id| QueryTxArchiveFn::new(canister_id, "get_transactions"),
Some(1_000_000usize),
);
assert_eq!(first_index, 0);
assert!(archived_transactions.is_empty());

local_blocks
}

/// Returns blocks in the specified range.
pub fn get_blocks(&self, start: BlockIndex, length: usize) -> GetBlocksResponse {
let (first_index, local_blocks, archived_blocks) = self.query_blocks(
start,
length,
encoded_block_to_generic_block,
|canister_id| QueryBlockArchiveFn::new(canister_id, "get_blocks"),
None,
);

GetBlocksResponse {
Expand Down Expand Up @@ -1088,6 +1112,7 @@ impl Ledger {
"icrc3_get_blocks",
)
},
None,
);
for (id, block) in (first_index..).zip(local_blocks) {
blocks.push(icrc_ledger_types::icrc3::blocks::BlockWithId {
Expand Down Expand Up @@ -1124,7 +1149,14 @@ impl Ledger {
}

pub fn is_ready() -> bool {
LEDGER_STATE.with(|s| matches!(*s.borrow(), LedgerState::Ready))
LEDGER_STATE.with(|s| {
matches!(*s.borrow(), LedgerState::Ready)
|| matches!(*s.borrow(), LedgerState::ReadyReadOnly)
})
}

pub fn is_read_only() -> bool {
LEDGER_STATE.with(|s| matches!(*s.borrow(), LedgerState::ReadyReadOnly))
}

pub fn panic_if_not_ready() {
Expand All @@ -1133,6 +1165,12 @@ pub fn panic_if_not_ready() {
}
}

pub fn panic_if_read_only() {
if is_read_only() {
ic_cdk::trap("The Ledger is read only");
}
}

pub fn ledger_state() -> LedgerState {
LEDGER_STATE.with(|s| *s.borrow())
}
Expand Down
Loading
Loading