Skip to content

Commit 167861a

Browse files
committed
Add timestamp to meta block
1 parent cb0a898 commit 167861a

File tree

10 files changed

+93
-28
lines changed

10 files changed

+93
-28
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

graph/src/components/store/traits.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,14 @@ pub trait ChainStore: Send + Sync + 'static {
353353
/// may purge any other blocks with that number
354354
fn confirm_block_hash(&self, number: BlockNumber, hash: &BlockHash) -> Result<usize, Error>;
355355

356-
/// Find the block with `block_hash` and return the network name and number
357-
fn block_number(&self, hash: &BlockHash) -> Result<Option<(String, BlockNumber)>, StoreError>;
356+
/// Find the block with `block_hash` and return the network name, number and timestamp if present.
357+
/// Currently, the timestamp is only returned if it's present in the top level block. This format is
358+
/// depends on the chain and the implementation of Blockchain::Block for the specific chain.
359+
/// eg: {"block": { "timestamp": 123123123 } }
360+
fn block_number(
361+
&self,
362+
hash: &BlockHash,
363+
) -> Result<Option<(String, BlockNumber, Option<String>)>, StoreError>;
358364

359365
/// Tries to retrieve all transactions receipts for a given block.
360366
async fn transaction_receipts_in_block(

graphql/src/schema/meta.graphql

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ type _Block_ {
5555
hash: Bytes
5656
"The block number"
5757
number: Int!
58+
"Timestamp of the block if available, format depends on the chain"
59+
timestamp: String
5860
}
5961

6062
enum _SubgraphErrorPolicy_ {

node/src/manager/commands/rewind.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn block_ptr(
3636
None => bail!("can not find chain store for {}", chain),
3737
Some(store) => store,
3838
};
39-
if let Some((_, number)) = chain_store.block_number(&block_ptr_to.hash)? {
39+
if let Some((_, number, _)) = chain_store.block_number(&block_ptr_to.hash)? {
4040
if number != block_ptr_to.number {
4141
bail!(
4242
"the given hash is for block number {} but the command specified block number {}",

server/index-node/src/resolver.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ impl<S: Store> IndexNodeResolver<S> {
228228
let chain_store = chain.chain_store();
229229
let call_cache = chain.call_cache();
230230

231-
let block_number = match chain_store.block_number(&block_hash) {
232-
Ok(Some((_, n))) => n,
231+
let (block_number, timestamp) = match chain_store.block_number(&block_hash) {
232+
Ok(Some((_, n, timestamp))) => (n, timestamp),
233233
Ok(None) => {
234234
error!(
235235
self.logger,
@@ -275,6 +275,7 @@ impl<S: Store> IndexNodeResolver<S> {
275275
block: object! {
276276
hash: cached_call.block_ptr.hash.hash_hex(),
277277
number: cached_call.block_ptr.number,
278+
timestamp: timestamp.clone(),
278279
},
279280
contractAddress: &cached_call.contract_address[..],
280281
returnValue: &cached_call.return_value[..],

store/postgres/src/chain_store.rs

+25-13
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mod data {
6969
use graph::prelude::{
7070
serde_json as json, BlockNumber, BlockPtr, CachedEthereumCall, Error, StoreError,
7171
};
72+
use serde::Deserialize;
7273
use std::fmt;
7374
use std::iter::FromIterator;
7475
use std::{convert::TryFrom, io::Write};
@@ -589,34 +590,42 @@ mod data {
589590
}
590591
}
591592

593+
/// timestamp's representation depends the blockchain::Block implementation, on
594+
/// ethereum this is a U256 but on different chains it will most likely be different.
592595
pub(super) fn block_number(
593596
&self,
594597
conn: &PgConnection,
595598
hash: &BlockHash,
596-
) -> Result<Option<BlockNumber>, StoreError> {
599+
) -> Result<Option<(BlockNumber, Option<String>)>, StoreError> {
600+
const TIMESTAMP_QUERY: &str =
601+
"coalesce(data->'block'->>'timestamp', data->>'timestamp')";
602+
597603
let number = match self {
598604
Storage::Shared => {
599605
use public::ethereum_blocks as b;
600606

601607
b::table
602-
.select(b::number)
608+
.select((b::number, sql(TIMESTAMP_QUERY)))
603609
.filter(b::hash.eq(format!("{:x}", hash)))
604-
.first::<i64>(conn)
610+
.first::<(i64, String)>(conn)
605611
.optional()?
606612
}
607613
Storage::Private(Schema { blocks, .. }) => blocks
608614
.table()
609-
.select(blocks.number())
615+
.select((blocks.number(), sql(TIMESTAMP_QUERY)))
610616
.filter(blocks.hash().eq(hash.as_slice()))
611-
.first::<i64>(conn)
617+
.first::<(i64, String)>(conn)
612618
.optional()?,
613619
};
614-
number
615-
.map(|number| {
616-
BlockNumber::try_from(number)
617-
.map_err(|e| StoreError::QueryExecutionError(e.to_string()))
618-
})
619-
.transpose()
620+
621+
match number {
622+
None => Ok(None),
623+
Some((number, ts)) => {
624+
let number = BlockNumber::try_from(number)
625+
.map_err(|e| StoreError::QueryExecutionError(e.to_string()))?;
626+
Ok(Some((number, Some(ts))))
627+
}
628+
}
620629
}
621630

622631
/// Find the first block that is missing from the database needed to
@@ -1687,12 +1696,15 @@ impl ChainStoreTrait for ChainStore {
16871696
.confirm_block_hash(&conn, &self.chain, number, hash)
16881697
}
16891698

1690-
fn block_number(&self, hash: &BlockHash) -> Result<Option<(String, BlockNumber)>, StoreError> {
1699+
fn block_number(
1700+
&self,
1701+
hash: &BlockHash,
1702+
) -> Result<Option<(String, BlockNumber, Option<String>)>, StoreError> {
16911703
let conn = self.get_conn()?;
16921704
Ok(self
16931705
.storage
16941706
.block_number(&conn, hash)?
1695-
.map(|number| (self.chain.clone(), number)))
1707+
.map(|(number, timestamp)| (self.chain.clone(), number, timestamp)))
16961708
}
16971709

16981710
async fn transaction_receipts_in_block(

store/postgres/src/query_store.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl QueryStoreTrait for QueryStore {
6868
let subgraph_network = self.network_name();
6969
self.chain_store
7070
.block_number(block_hash)?
71-
.map(|(network_name, number)| {
71+
.map(|(network_name, number, _timestamp)| {
7272
if network_name == subgraph_network {
7373
Ok(number)
7474
} else {

store/postgres/tests/store.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,40 @@ fn cleanup_cached_blocks() {
19811981
})
19821982
}
19831983

1984+
#[test]
1985+
/// checks if retrieving the timestamp from the data blob works.
1986+
/// on ethereum, the block has timestamp as U256 so it will always have a value
1987+
fn parse_timestamp() {
1988+
const EXPECTED_TS: &str = "0x62ceae26";
1989+
1990+
run_test(|store, _, _| async move {
1991+
use block_store::*;
1992+
// The test subgraph is at block 2. Since we don't ever delete
1993+
// the genesis block, the only block eligible for cleanup is BLOCK_ONE
1994+
// and the first retained block is block 2.
1995+
block_store::set_chain(
1996+
vec![
1997+
&*GENESIS_BLOCK,
1998+
&*BLOCK_ONE,
1999+
&*BLOCK_TWO,
2000+
&*BLOCK_THREE_TIMESTAMP,
2001+
],
2002+
NETWORK_NAME,
2003+
);
2004+
let chain_store = store
2005+
.block_store()
2006+
.chain_store(NETWORK_NAME)
2007+
.expect("fake chain store");
2008+
2009+
let (_network, number, timestamp) = chain_store
2010+
.block_number(&BLOCK_THREE_TIMESTAMP.block_hash())
2011+
.expect("block_number to return correct number and timestamp")
2012+
.unwrap();
2013+
assert_eq!(number, 3);
2014+
assert_eq!(timestamp.unwrap(), EXPECTED_TS);
2015+
})
2016+
}
2017+
19842018
#[test]
19852019
fn reorg_tracking() {
19862020
async fn update_john(

store/test-store/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ lazy_static = "1.1"
1616
hex-literal = "0.3"
1717
diesel = { version = "1.4.8", features = ["postgres", "serde_json", "numeric", "r2d2"] }
1818
graph-chain-ethereum = { path = "../../chain/ethereum" }
19+
serde = "1.0"

store/test-store/src/block_store.rs

+17-9
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use graph::components::store::BlockStore;
66
use graph::{
77
blockchain::Block,
88
prelude::{
9-
serde_json, web3::types::H256, BlockHash, BlockNumber, BlockPtr, EthereumBlock,
10-
LightEthereumBlock,
9+
serde_json, web3::types::H256, web3::types::U256, BlockHash, BlockNumber, BlockPtr,
10+
EthereumBlock, LightEthereumBlock,
1111
},
1212
};
1313

@@ -16,23 +16,25 @@ lazy_static! {
1616
pub static ref GENESIS_BLOCK: FakeBlock = FakeBlock {
1717
number: super::GENESIS_PTR.number,
1818
hash: super::GENESIS_PTR.hash_hex(),
19+
timestamp: None,
1920
parent_hash: NO_PARENT.to_string()
2021
};
2122
pub static ref BLOCK_ONE: FakeBlock = GENESIS_BLOCK
22-
.make_child("8511fa04b64657581e3f00e14543c1d522d5d7e771b54aa3060b662ade47da13");
23+
.make_child("8511fa04b64657581e3f00e14543c1d522d5d7e771b54aa3060b662ade47da13", None);
2324
pub static ref BLOCK_ONE_SIBLING: FakeBlock =
24-
GENESIS_BLOCK.make_child("b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1");
25+
GENESIS_BLOCK.make_child("b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1", None);
2526
pub static ref BLOCK_ONE_NO_PARENT: FakeBlock = FakeBlock::make_no_parent(
2627
1,
2728
"7205bdfcf4521874cf38ce38c879ff967bf3a069941286bfe267109ad275a63d"
2829
);
2930

30-
pub static ref BLOCK_TWO: FakeBlock = BLOCK_ONE.make_child("f8ccbd3877eb98c958614f395dd351211afb9abba187bfc1fb4ac414b099c4a6");
31+
pub static ref BLOCK_TWO: FakeBlock = BLOCK_ONE.make_child("f8ccbd3877eb98c958614f395dd351211afb9abba187bfc1fb4ac414b099c4a6", None);
3132
pub static ref BLOCK_TWO_NO_PARENT: FakeBlock = FakeBlock::make_no_parent(2, "3b652b00bff5e168b1218ff47593d516123261c4487629c4175f642ee56113fe");
32-
pub static ref BLOCK_THREE: FakeBlock = BLOCK_TWO.make_child("7347afe69254df06729e123610b00b8b11f15cfae3241f9366fb113aec07489c");
33+
pub static ref BLOCK_THREE: FakeBlock = BLOCK_TWO.make_child("7347afe69254df06729e123610b00b8b11f15cfae3241f9366fb113aec07489c", None);
3334
pub static ref BLOCK_THREE_NO_PARENT: FakeBlock = FakeBlock::make_no_parent(3, "fa9ebe3f74de4c56908b49f5c4044e85825f7350f3fa08a19151de82a82a7313");
34-
pub static ref BLOCK_FOUR: FakeBlock = BLOCK_THREE.make_child("7cce080f5a49c2997a6cc65fc1cee9910fd8fc3721b7010c0b5d0873e2ac785e");
35-
pub static ref BLOCK_FIVE: FakeBlock = BLOCK_FOUR.make_child("7b0ea919e258eb2b119eb32de56b85d12d50ac6a9f7c5909f843d6172c8ba196");
35+
pub static ref BLOCK_THREE_TIMESTAMP: FakeBlock = BLOCK_TWO.make_child("6b834521bb753c132fdcf0e1034803ed9068e324112f8750ba93580b393a986b", Some(U256::from(1657712166)));
36+
pub static ref BLOCK_FOUR: FakeBlock = BLOCK_THREE.make_child("7cce080f5a49c2997a6cc65fc1cee9910fd8fc3721b7010c0b5d0873e2ac785e", None);
37+
pub static ref BLOCK_FIVE: FakeBlock = BLOCK_FOUR.make_child("7b0ea919e258eb2b119eb32de56b85d12d50ac6a9f7c5909f843d6172c8ba196", None);
3638
pub static ref BLOCK_SIX_NO_PARENT: FakeBlock = FakeBlock::make_no_parent(6, "6b834521bb753c132fdcf0e1034803ed9068e324112f8750ba93580b393a986b");
3739
}
3840

@@ -45,14 +47,16 @@ pub struct FakeBlock {
4547
pub number: BlockNumber,
4648
pub hash: String,
4749
pub parent_hash: String,
50+
pub timestamp: Option<U256>,
4851
}
4952

5053
impl FakeBlock {
51-
pub fn make_child(&self, hash: &str) -> Self {
54+
pub fn make_child(&self, hash: &str, timestamp: Option<U256>) -> Self {
5255
FakeBlock {
5356
number: self.number + 1,
5457
hash: hash.to_owned(),
5558
parent_hash: self.hash.clone(),
59+
timestamp,
5660
}
5761
}
5862

@@ -61,6 +65,7 @@ impl FakeBlock {
6165
number,
6266
hash: hash.to_owned(),
6367
parent_hash: NO_PARENT.to_string(),
68+
timestamp: None,
6469
}
6570
}
6671

@@ -79,6 +84,9 @@ impl FakeBlock {
7984
block.number = Some(self.number.into());
8085
block.parent_hash = parent_hash;
8186
block.hash = Some(H256(self.block_hash().as_slice().try_into().unwrap()));
87+
if let Some(ts) = self.timestamp {
88+
block.timestamp = ts;
89+
}
8290

8391
EthereumBlock {
8492
block: Arc::new(block),

0 commit comments

Comments
 (0)