Skip to content

Commit c99229c

Browse files
committed
core, graph, runtime: Add store.get_in_block
1 parent 640d15d commit c99229c

File tree

6 files changed

+162
-73
lines changed

6 files changed

+162
-73
lines changed

core/src/subgraph/runner.rs

+8-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::subgraph::stream::new_block_stream;
66
use atomic_refcell::AtomicRefCell;
77
use graph::blockchain::block_stream::{BlockStreamEvent, BlockWithTriggers, FirehoseCursor};
88
use graph::blockchain::{Block, Blockchain, DataSource as _, TriggerFilter as _};
9-
use graph::components::store::{EmptyStore, EntityKey, StoredDynamicDataSource};
9+
use graph::components::store::{EmptyStore, EntityKey, GetScope, StoredDynamicDataSource};
1010
use graph::components::{
1111
store::ModificationsAndCache,
1212
subgraph::{MappingError, PoICausalityRegion, ProofOfIndexing, SharedProofOfIndexing},
@@ -1034,14 +1034,13 @@ async fn update_proof_of_indexing(
10341034
};
10351035

10361036
// Grab the current digest attribute on this entity
1037-
let prev_poi =
1038-
entity_cache
1039-
.get(&entity_key)
1040-
.map_err(Error::from)?
1041-
.map(|entity| match entity.get("digest") {
1042-
Some(Value::Bytes(b)) => b.clone(),
1043-
_ => panic!("Expected POI entity to have a digest and for it to be bytes"),
1044-
});
1037+
let prev_poi = entity_cache
1038+
.get(&entity_key, GetScope::Store)
1039+
.map_err(Error::from)?
1040+
.map(|entity| match entity.get("digest") {
1041+
Some(Value::Bytes(b)) => b.clone(),
1042+
_ => panic!("Expected POI entity to have a digest and for it to be bytes"),
1043+
});
10451044

10461045
// Finish the POI stream, getting the new POI value.
10471046
let updated_proof_of_indexing = stream.pause(prev_poi.as_deref());

graph/src/components/store/entity_cache.rs

+26-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ use crate::util::lfu_cache::LfuCache;
99

1010
use super::{DerivedEntityQuery, EntityType, LoadRelatedRequest};
1111

12+
/// The scope in which the `EntityCache` should perform a `get` operation
13+
pub enum GetScope {
14+
/// Get from all previously stored entities in the store
15+
Store,
16+
/// Get from the entities that have been stored during this block
17+
InBlock,
18+
}
19+
1220
/// A cache for entities from the store that provides the basic functionality
1321
/// needed for the store interactions in the host exports. This struct tracks
1422
/// how entities are modified, and caches all entities looked up from the
@@ -98,18 +106,29 @@ impl EntityCache {
98106
self.handler_updates.clear();
99107
}
100108

101-
pub fn get(&mut self, eref: &EntityKey) -> Result<Option<Entity>, s::QueryExecutionError> {
109+
pub fn get(
110+
&mut self,
111+
key: &EntityKey,
112+
scope: GetScope,
113+
) -> Result<Option<Entity>, s::QueryExecutionError> {
102114
// Get the current entity, apply any updates from `updates`, then
103115
// from `handler_updates`.
104-
let mut entity = self.current.get_entity(&*self.store, eref)?;
116+
let mut entity = match scope {
117+
GetScope::Store => self.current.get_entity(&*self.store, key)?,
118+
GetScope::InBlock => None,
119+
};
105120

106-
// Always test the cache consistency in debug mode.
107-
debug_assert!(entity == self.store.get(eref).unwrap());
121+
// Always test the cache consistency in debug mode. The test only
122+
// makes sense when we were actually asked to read from the store
123+
debug_assert!(match scope {
124+
GetScope::Store => entity == self.store.get(key).unwrap(),
125+
GetScope::InBlock => true,
126+
});
108127

109-
if let Some(op) = self.updates.get(eref).cloned() {
128+
if let Some(op) = self.updates.get(key).cloned() {
110129
entity = op.apply_to(entity)
111130
}
112-
if let Some(op) = self.handler_updates.get(eref).cloned() {
131+
if let Some(op) = self.handler_updates.get(key).cloned() {
113132
entity = op.apply_to(entity)
114133
}
115134
Ok(entity)
@@ -183,7 +202,7 @@ impl EntityCache {
183202
// lookup in the database and check again with an entity that merges
184203
// the existing entity with the changes
185204
if !is_valid {
186-
let entity = self.get(&key)?.ok_or_else(|| {
205+
let entity = self.get(&key, GetScope::Store)?.ok_or_else(|| {
187206
anyhow!(
188207
"Failed to read entity {}[{}] back from cache",
189208
key.entity_type,

graph/src/components/store/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod entity_cache;
22
mod err;
33
mod traits;
44

5-
pub use entity_cache::{EntityCache, ModificationsAndCache};
5+
pub use entity_cache::{EntityCache, GetScope, ModificationsAndCache};
66

77
use diesel::types::{FromSql, ToSql};
88
pub use err::StoreError;

runtime/wasm/src/host_exports.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use wasmtime::Trap;
99
use web3::types::H160;
1010

1111
use graph::blockchain::Blockchain;
12-
use graph::components::store::{EnsLookup, LoadRelatedRequest};
12+
use graph::components::store::{EnsLookup, GetScope, LoadRelatedRequest};
1313
use graph::components::store::{EntityKey, EntityType};
1414
use graph::components::subgraph::{
1515
PoICausalityRegion, ProofOfIndexingEvent, SharedProofOfIndexing,
@@ -225,6 +225,7 @@ impl<C: Blockchain> HostExports<C> {
225225
entity_type: String,
226226
entity_id: String,
227227
gas: &GasCounter,
228+
scope: GetScope,
228229
) -> Result<Option<Entity>, anyhow::Error> {
229230
let store_key = EntityKey {
230231
entity_type: EntityType::new(entity_type),
@@ -233,7 +234,7 @@ impl<C: Blockchain> HostExports<C> {
233234
};
234235
self.check_entity_type_access(&store_key.entity_type)?;
235236

236-
let result = state.entity_cache.get(&store_key)?;
237+
let result = state.entity_cache.get(&store_key, scope)?;
237238
gas.consume_host_fn(gas::STORE_GET.with_args(complexity::Linear, (&store_key, &result)))?;
238239

239240
Ok(result)

runtime/wasm/src/module/mod.rs

+83-52
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::time::Instant;
99

1010
use anyhow::anyhow;
1111
use anyhow::Error;
12+
use graph::components::store::GetScope;
1213
use graph::slog::SendSyncRefUnwindSafeKV;
1314
use never::Never;
1415
use semver::Version;
@@ -535,6 +536,13 @@ impl<C: Blockchain> WasmInstance<C> {
535536
id,
536537
field
537538
);
539+
link!(
540+
"store.get_in_block",
541+
store_get_in_block,
542+
"host_export_store_get_in_block",
543+
entity,
544+
id
545+
);
538546
link!(
539547
"store.set",
540548
store_set,
@@ -910,6 +918,71 @@ impl<C: Blockchain> WasmInstanceContext<C> {
910918
experimental_features,
911919
})
912920
}
921+
922+
fn store_get_scoped(
923+
&mut self,
924+
gas: &GasCounter,
925+
entity_ptr: AscPtr<AscString>,
926+
id_ptr: AscPtr<AscString>,
927+
scope: GetScope,
928+
) -> Result<AscPtr<AscEntity>, HostExportError> {
929+
let _timer = self
930+
.host_metrics
931+
.cheap_clone()
932+
.time_host_fn_execution_region("store_get");
933+
934+
let entity_type: String = asc_get(self, entity_ptr, gas)?;
935+
let id: String = asc_get(self, id_ptr, gas)?;
936+
let entity_option = self.ctx.host_exports.store_get(
937+
&mut self.ctx.state,
938+
entity_type.clone(),
939+
id.clone(),
940+
gas,
941+
scope,
942+
)?;
943+
944+
if self.ctx.instrument {
945+
debug!(self.ctx.logger, "store_get";
946+
"type" => &entity_type,
947+
"id" => &id,
948+
"found" => entity_option.is_some());
949+
}
950+
951+
let ret = match entity_option {
952+
Some(entity) => {
953+
let _section = self
954+
.host_metrics
955+
.stopwatch
956+
.start_section("store_get_asc_new");
957+
asc_new(self, &entity.sorted(), gas)?
958+
}
959+
None => match &self.ctx.debug_fork {
960+
Some(fork) => {
961+
let entity_option = fork.fetch(entity_type, id).map_err(|e| {
962+
HostExportError::Unknown(anyhow!(
963+
"store_get: failed to fetch entity from the debug fork: {}",
964+
e
965+
))
966+
})?;
967+
match entity_option {
968+
Some(entity) => {
969+
let _section = self
970+
.host_metrics
971+
.stopwatch
972+
.start_section("store_get_asc_new");
973+
let entity = asc_new(self, &entity.sorted(), gas)?;
974+
self.store_set(gas, entity_ptr, id_ptr, entity)?;
975+
entity
976+
}
977+
None => AscPtr::null(),
978+
}
979+
}
980+
None => AscPtr::null(),
981+
},
982+
};
983+
984+
Ok(ret)
985+
}
913986
}
914987

915988
// Implementation of externals.
@@ -1012,59 +1085,17 @@ impl<C: Blockchain> WasmInstanceContext<C> {
10121085
entity_ptr: AscPtr<AscString>,
10131086
id_ptr: AscPtr<AscString>,
10141087
) -> Result<AscPtr<AscEntity>, HostExportError> {
1015-
let _timer = self
1016-
.host_metrics
1017-
.cheap_clone()
1018-
.time_host_fn_execution_region("store_get");
1019-
1020-
let entity_type: String = asc_get(self, entity_ptr, gas)?;
1021-
let id: String = asc_get(self, id_ptr, gas)?;
1022-
let entity_option = self.ctx.host_exports.store_get(
1023-
&mut self.ctx.state,
1024-
entity_type.clone(),
1025-
id.clone(),
1026-
gas,
1027-
)?;
1028-
if self.ctx.instrument {
1029-
debug!(self.ctx.logger, "store_get";
1030-
"type" => &entity_type,
1031-
"id" => &id,
1032-
"found" => entity_option.is_some());
1033-
}
1034-
let ret = match entity_option {
1035-
Some(entity) => {
1036-
let _section = self
1037-
.host_metrics
1038-
.stopwatch
1039-
.start_section("store_get_asc_new");
1040-
asc_new(self, &entity.sorted(), gas)?
1041-
}
1042-
None => match &self.ctx.debug_fork {
1043-
Some(fork) => {
1044-
let entity_option = fork.fetch(entity_type, id).map_err(|e| {
1045-
HostExportError::Unknown(anyhow!(
1046-
"store_get: failed to fetch entity from the debug fork: {}",
1047-
e
1048-
))
1049-
})?;
1050-
match entity_option {
1051-
Some(entity) => {
1052-
let _section = self
1053-
.host_metrics
1054-
.stopwatch
1055-
.start_section("store_get_asc_new");
1056-
let entity = asc_new(self, &entity.sorted(), gas)?;
1057-
self.store_set(gas, entity_ptr, id_ptr, entity)?;
1058-
entity
1059-
}
1060-
None => AscPtr::null(),
1061-
}
1062-
}
1063-
None => AscPtr::null(),
1064-
},
1065-
};
1088+
self.store_get_scoped(gas, entity_ptr, id_ptr, GetScope::Store)
1089+
}
10661090

1067-
Ok(ret)
1091+
/// function store.get_in_block(entity: string, id: string): Entity | null
1092+
pub fn store_get_in_block(
1093+
&mut self,
1094+
gas: &GasCounter,
1095+
entity_ptr: AscPtr<AscString>,
1096+
id_ptr: AscPtr<AscString>,
1097+
) -> Result<AscPtr<AscEntity>, HostExportError> {
1098+
self.store_get_scoped(gas, entity_ptr, id_ptr, GetScope::InBlock)
10681099
}
10691100

10701101
/// function store.loadRelated(entity_type: string, id: string, field: string): Array<Entity>

store/test-store/tests/graph/entity_cache.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use graph::blockchain::block_stream::FirehoseCursor;
22
use graph::components::store::{
3-
DeploymentCursorTracker, DerivedEntityQuery, EntityKey, EntityType, LoadRelatedRequest,
4-
ReadStore, StoredDynamicDataSource, WritableStore,
3+
DeploymentCursorTracker, DerivedEntityQuery, EntityKey, EntityType, GetScope,
4+
LoadRelatedRequest, ReadStore, StoredDynamicDataSource, WritableStore,
55
};
66
use graph::data::subgraph::schema::{DeploymentCreate, SubgraphError, SubgraphHealth};
77
use graph::data_source::CausalityRegion;
@@ -752,3 +752,42 @@ fn check_for_delete_async_related() {
752752
assert_eq!(result, expeted_vec);
753753
});
754754
}
755+
756+
#[test]
757+
fn scoped_get() {
758+
run_store_test(|mut cache, _store, _deployment, _writable| async move {
759+
// Key for an existing entity that is in the store
760+
let key1 = EntityKey::data(WALLET.to_owned(), "1".to_owned());
761+
let wallet1 = create_wallet_entity("1", "1", 67);
762+
763+
// Create a new entity that is not in the store
764+
let wallet5 = create_wallet_entity("5", "5", 100);
765+
let key5 = EntityKey::data(WALLET.to_owned(), "5".to_owned());
766+
cache.set(key5.clone(), wallet5.clone()).unwrap();
767+
768+
// For the new entity, we can retrieve it with either scope
769+
let act5 = cache.get(&key5, GetScope::InBlock).unwrap();
770+
assert_eq!(Some(&wallet5), act5.as_ref());
771+
let act5 = cache.get(&key5, GetScope::Store).unwrap();
772+
assert_eq!(Some(&wallet5), act5.as_ref());
773+
774+
// For an entity in the store, we can not get it `InBlock` but with
775+
// `Store`
776+
let act1 = cache.get(&key1, GetScope::InBlock).unwrap();
777+
assert_eq!(None, act1);
778+
let act1 = cache.get(&key1, GetScope::Store).unwrap();
779+
assert_eq!(Some(&wallet1), act1.as_ref());
780+
// Even after reading from the store, the entity is not visible with
781+
// `InBlock`
782+
let act1 = cache.get(&key1, GetScope::InBlock).unwrap();
783+
assert_eq!(None, act1);
784+
// But if it gets updated, it becomes visible with either scope
785+
let mut wallet1 = wallet1;
786+
wallet1.set("balance", 70);
787+
cache.set(key1.clone(), wallet1.clone()).unwrap();
788+
let act1 = cache.get(&key1, GetScope::InBlock).unwrap();
789+
assert_eq!(Some(&wallet1), act1.as_ref());
790+
let act1 = cache.get(&key1, GetScope::Store).unwrap();
791+
assert_eq!(Some(&wallet1), act1.as_ref());
792+
});
793+
}

0 commit comments

Comments
 (0)