Skip to content

Commit ebcf07c

Browse files
authored
Account for published blobs in the resource at the end of transaction execution rather than upfront. (#3995)
## Motivation We are charging for all published blobs before we execute any operation in the block. This is wrong b/c we want the incoming grant to be able to cover the fees. The grants are processed during operation processing only so they are not taken into account at the beginning of the block processing. ## Proposal Move the published blobs' resources tracking to the end of processing a transaction that published it. At this point any incoming grant is already processed. In order to do so, we track blob IDs referenced during transaction execution via `TransactionTracker.published_blobs` and consume it in `BlockExecutionTracker::process_txn_outcome`. Also, charge for publishing user-created blobs only. `BlobType::Committee` is not accounted for. ## Test Plan CI. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 38f8535 commit ebcf07c

File tree

7 files changed

+93
-32
lines changed

7 files changed

+93
-32
lines changed

linera-base/src/data_types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,11 @@ impl Blob {
13741374
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
13751375
Ok(Self::new_data(fs::read(path)?))
13761376
}
1377+
1378+
/// Returns whether the blob is of [`BlobType::Committee`] variant.
1379+
pub fn is_committee_blob(&self) -> bool {
1380+
self.content().blob_type().is_committee_blob()
1381+
}
13771382
}
13781383

13791384
impl Serialize for Blob {

linera-base/src/identifiers.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,21 @@ pub enum BlobType {
169169
ChainDescription,
170170
}
171171

172+
impl BlobType {
173+
/// Returns whether the blob is of [`BlobType::Committee`] variant.
174+
pub fn is_committee_blob(&self) -> bool {
175+
match self {
176+
BlobType::Data
177+
| BlobType::ContractBytecode
178+
| BlobType::ServiceBytecode
179+
| BlobType::EvmBytecode
180+
| BlobType::ApplicationDescription
181+
| BlobType::ChainDescription => false,
182+
BlobType::Committee => true,
183+
}
184+
}
185+
}
186+
172187
impl fmt::Display for BlobType {
173188
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174189
write!(f, "{:?}", self)

linera-chain/src/block_tracker.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::collections::BTreeSet;
4+
use std::collections::{BTreeMap, BTreeSet};
55

66
use custom_debug_derive::Debug;
77
use linera_base::{
88
data_types::{Blob, Event, OracleResponse, Timestamp},
9-
identifiers::{AccountOwner, BlobType, ChainId},
9+
identifiers::{AccountOwner, BlobId, ChainId},
1010
};
1111
use linera_execution::{
1212
OutgoingMessage, ResourceController, ResourceTracker, SystemExecutionStateView,
@@ -23,7 +23,7 @@ use crate::{
2323
/// Tracks execution of transactions within a block.
2424
/// Captures the resource policy, produced messages, oracle responses and events.
2525
#[derive(Debug)]
26-
pub struct BlockExecutionTracker<'resources> {
26+
pub struct BlockExecutionTracker<'resources, 'blobs> {
2727
resource_controller: &'resources mut ResourceController<Option<AccountOwner>, ResourceTracker>,
2828
local_time: Timestamp,
2929
#[debug(skip_if = Option::is_none)]
@@ -44,17 +44,21 @@ pub struct BlockExecutionTracker<'resources> {
4444
// Index of the currently executed transaction in a block.
4545
transaction_index: u32,
4646

47+
// Blobs published in the block.
48+
published_blobs: BTreeMap<BlobId, &'blobs Blob>,
49+
4750
// We expect the number of outcomes to be equal to the number of transactions in the block.
4851
expected_outcomes_count: usize,
4952
}
5053

51-
impl<'resources> BlockExecutionTracker<'resources> {
54+
impl<'resources, 'blobs> BlockExecutionTracker<'resources, 'blobs> {
5255
/// Creates a new BlockExecutionTracker.
5356
pub fn new(
5457
resource_controller: &'resources mut ResourceController<
5558
Option<AccountOwner>,
5659
ResourceTracker,
5760
>,
61+
published_blobs: BTreeMap<BlobId, &'blobs Blob>,
5862
local_time: Timestamp,
5963
replaying_oracle_responses: Option<Vec<Vec<OracleResponse>>>,
6064
proposal: &ProposedBlock,
@@ -76,6 +80,7 @@ impl<'resources> BlockExecutionTracker<'resources> {
7680
messages: Vec::new(),
7781
operation_results: Vec::new(),
7882
transaction_index: 0,
83+
published_blobs,
7984
expected_outcomes_count: proposal.incoming_bundles.len() + proposal.operations.len(),
8085
})
8186
}
@@ -149,11 +154,23 @@ impl<'resources> BlockExecutionTracker<'resources> {
149154
))
150155
.with_execution_context(context)?;
151156

157+
// Account for blobs published by this transaction directly.
152158
for blob in &txn_outcome.blobs {
153-
if blob.content().blob_type() == BlobType::Data {
159+
resource_controller
160+
.track_blob_published(blob)
161+
.with_execution_context(context)?;
162+
}
163+
164+
// Account for blobs published indirectly but referenced by the transaction.
165+
for blob_id in &txn_outcome.blobs_published {
166+
if let Some(blob) = self.published_blobs.get(blob_id) {
154167
resource_controller
155-
.track_blob_published(blob.content())
168+
.track_blob_published(blob)
156169
.with_execution_context(context)?;
170+
} else {
171+
return Err(ChainError::InternalError(format!(
172+
"Missing published blob {blob_id}"
173+
)));
157174
}
158175
}
159176

linera-chain/src/chain.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -726,32 +726,28 @@ where
726726
block.authenticated_signer,
727727
);
728728

729+
for blob in published_blobs {
730+
let blob_id = blob.id();
731+
resource_controller
732+
.policy()
733+
.check_blob_size(blob.content())
734+
.with_execution_context(ChainExecutionContext::Block)?;
735+
chain.system.used_blobs.insert(&blob_id)?;
736+
}
737+
729738
// Execute each incoming bundle as a transaction, then each operation.
730739
// Collect messages, events and oracle responses, each as one list per transaction.
731740
let mut block_execution_tracker = BlockExecutionTracker::new(
732741
&mut resource_controller,
742+
published_blobs
743+
.iter()
744+
.map(|blob| (blob.id(), blob))
745+
.collect(),
733746
local_time,
734747
replaying_oracle_responses,
735748
block,
736749
)?;
737750

738-
for blob in published_blobs {
739-
let blob_type = blob.content().blob_type();
740-
if blob_type == BlobType::Data
741-
|| blob_type == BlobType::ContractBytecode
742-
|| blob_type == BlobType::ServiceBytecode
743-
|| blob_type == BlobType::EvmBytecode
744-
{
745-
block_execution_tracker
746-
.resource_controller_mut()
747-
.with_state(&mut chain.system)
748-
.await?
749-
.track_blob_published(blob.content())
750-
.with_execution_context(ChainExecutionContext::Block)?;
751-
}
752-
chain.system.used_blobs.insert(&blob.id())?;
753-
}
754-
755751
for transaction in block.transactions() {
756752
let chain_execution_context =
757753
block_execution_tracker.chain_execution_context(&transaction);

linera-execution/src/resources.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{sync::Arc, time::Duration};
77

88
use custom_debug_derive::Debug;
99
use linera_base::{
10-
data_types::{Amount, ArithmeticError, BlobContent},
10+
data_types::{Amount, ArithmeticError, Blob},
1111
ensure,
1212
identifiers::AccountOwner,
1313
vm::VmRuntime,
@@ -338,9 +338,12 @@ where
338338
}
339339

340340
/// Tracks a number of blob bytes published.
341-
pub fn track_blob_published(&mut self, content: &BlobContent) -> Result<(), ExecutionError> {
342-
self.policy.check_blob_size(content)?;
343-
let size = content.bytes().len() as u64;
341+
pub fn track_blob_published(&mut self, blob: &Blob) -> Result<(), ExecutionError> {
342+
self.policy.check_blob_size(blob.content())?;
343+
let size = blob.content().bytes().len() as u64;
344+
if blob.is_committee_blob() {
345+
return Ok(());
346+
}
344347
{
345348
let tracker = self.tracker.as_mut();
346349
tracker.blob_bytes_published = tracker

linera-execution/src/system.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,10 @@ where
430430
);
431431
match admin_operation {
432432
AdminOperation::PublishCommitteeBlob { blob_hash } => {
433-
self.blob_published(&BlobId::new(blob_hash, BlobType::Committee))?;
433+
self.blob_published(
434+
&BlobId::new(blob_hash, BlobType::Committee),
435+
txn_tracker,
436+
)?;
434437
}
435438
AdminOperation::CreateCommittee { epoch, blob_hash } => {
436439
self.check_next_epoch(epoch)?;
@@ -461,7 +464,7 @@ where
461464
}
462465
PublishModule { module_id } => {
463466
for blob_id in module_id.bytecode_blob_ids() {
464-
self.blob_published(&blob_id)?;
467+
self.blob_published(&blob_id, txn_tracker)?;
465468
}
466469
}
467470
CreateApplication {
@@ -488,7 +491,7 @@ where
488491
new_application = Some((app_id, instantiation_argument));
489492
}
490493
PublishDataBlob { blob_hash } => {
491-
self.blob_published(&BlobId::new(blob_hash, BlobType::Data))?;
494+
self.blob_published(&BlobId::new(blob_hash, BlobType::Data), txn_tracker)?;
492495
}
493496
ReadBlob { blob_id } => {
494497
let content = self.read_blob_content(blob_id).await?;
@@ -982,8 +985,13 @@ where
982985

983986
/// Records a blob that is published in this block. This does not create an oracle entry, and
984987
/// the blob can be used without using an oracle in the future on this chain.
985-
fn blob_published(&mut self, blob_id: &BlobId) -> Result<(), ExecutionError> {
988+
fn blob_published(
989+
&mut self,
990+
blob_id: &BlobId,
991+
txn_tracker: &mut TransactionTracker,
992+
) -> Result<(), ExecutionError> {
986993
self.used_blobs.insert(blob_id)?;
994+
txn_tracker.add_published_blob(*blob_id);
987995
Ok(())
988996
}
989997

linera-execution/src/transaction_tracker.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::{collections::BTreeMap, mem, vec};
4+
use std::{
5+
collections::{BTreeMap, BTreeSet},
6+
mem, vec,
7+
};
58

69
use custom_debug_derive::Debug;
710
use linera_base::{
@@ -34,11 +37,17 @@ pub struct TransactionTracker {
3437
/// Events recorded by contracts' `emit` calls.
3538
events: Vec<Event>,
3639
/// Blobs created by contracts.
40+
///
41+
/// As of right now, blobs created by the contracts are one of the two:
42+
/// - [`OpenChain`]
43+
/// - [`CreateApplication`]
3744
blobs: BTreeMap<BlobId, Blob>,
3845
/// Operation result.
3946
operation_result: Option<Vec<u8>>,
4047
/// Streams that have been updated but not yet processed during this transaction.
4148
streams_to_process: BTreeMap<ApplicationId, AppStreamUpdates>,
49+
/// Published blobs this transaction refers to by [`BlobId`].
50+
blobs_published: BTreeSet<BlobId>,
4251
}
4352

4453
/// The [`TransactionTracker`] contents after a transaction has finished.
@@ -57,6 +66,8 @@ pub struct TransactionOutcome {
5766
pub blobs: Vec<Blob>,
5867
/// Operation result.
5968
pub operation_result: Vec<u8>,
69+
/// Blobs published by this transaction.
70+
pub blobs_published: BTreeSet<BlobId>,
6071
}
6172

6273
impl TransactionTracker {
@@ -146,6 +157,10 @@ impl TransactionTracker {
146157
self.blobs.insert(blob.id(), blob);
147158
}
148159

160+
pub fn add_published_blob(&mut self, blob_id: BlobId) {
161+
self.blobs_published.insert(blob_id);
162+
}
163+
149164
pub fn created_blobs(&self) -> &BTreeMap<BlobId, Blob> {
150165
&self.blobs
151166
}
@@ -266,6 +281,7 @@ impl TransactionTracker {
266281
blobs,
267282
operation_result,
268283
streams_to_process,
284+
blobs_published,
269285
} = self;
270286
ensure!(
271287
streams_to_process.is_empty(),
@@ -286,6 +302,7 @@ impl TransactionTracker {
286302
events,
287303
blobs: blobs.into_values().collect(),
288304
operation_result: operation_result.unwrap_or_default(),
305+
blobs_published,
289306
})
290307
}
291308
}

0 commit comments

Comments
 (0)