diff --git a/client/rpc-core/src/types/block_number.rs b/client/rpc-core/src/types/block_number.rs index 1ece65e96f..06ab5e946a 100644 --- a/client/rpc-core/src/types/block_number.rs +++ b/client/rpc-core/src/types/block_number.rs @@ -41,6 +41,12 @@ pub enum BlockNumber { Earliest, /// Pending block (being mined) Pending, + /// The most recent crypto-economically secure block. + /// There is no difference between Ethereum's `safe` and `finalized` + /// in Substrate finality gadget. + Safe, + /// The most recent crypto-economically secure block. + Finalized, } impl Default for BlockNumber { @@ -86,6 +92,8 @@ impl Serialize for BlockNumber { BlockNumber::Latest => serializer.serialize_str("latest"), BlockNumber::Earliest => serializer.serialize_str("earliest"), BlockNumber::Pending => serializer.serialize_str("pending"), + BlockNumber::Safe => serializer.serialize_str("safe"), + BlockNumber::Finalized => serializer.serialize_str("finalized"), } } } @@ -98,7 +106,7 @@ impl<'a> Visitor<'a> for BlockNumberVisitor { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!( formatter, - "a block number or 'latest', 'earliest' or 'pending'" + "a block number or 'latest', 'safe', 'finalized', 'earliest' or 'pending'" ) } @@ -163,6 +171,8 @@ impl<'a> Visitor<'a> for BlockNumberVisitor { "latest" => Ok(BlockNumber::Latest), "earliest" => Ok(BlockNumber::Earliest), "pending" => Ok(BlockNumber::Pending), + "safe" => Ok(BlockNumber::Safe), + "finalized" => Ok(BlockNumber::Finalized), _ if value.starts_with("0x") => u64::from_str_radix(&value[2..], 16) .map(BlockNumber::Num) .map_err(|e| Error::custom(format!("Invalid block number: {}", e))), @@ -196,6 +206,8 @@ mod tests { BlockNumber::Num(number) => Some(number), BlockNumber::Earliest => Some(0), BlockNumber::Latest => Some(1000), + BlockNumber::Safe => Some(999), + BlockNumber::Finalized => Some(999), BlockNumber::Pending => Some(1001), _ => None, } @@ -208,6 +220,8 @@ mod tests { let bn_u64: BlockNumber = serde_json::from_str(r#"420"#).unwrap(); let bn_tag_earliest: BlockNumber = serde_json::from_str(r#""earliest""#).unwrap(); let bn_tag_latest: BlockNumber = serde_json::from_str(r#""latest""#).unwrap(); + let bn_tag_safe: BlockNumber = serde_json::from_str(r#""safe""#).unwrap(); + let bn_tag_finalized: BlockNumber = serde_json::from_str(r#""finalized""#).unwrap(); let bn_tag_pending: BlockNumber = serde_json::from_str(r#""pending""#).unwrap(); assert_eq!(match_block_number(bn_dec).unwrap(), 42); @@ -215,6 +229,8 @@ mod tests { assert_eq!(match_block_number(bn_u64).unwrap(), 420); assert_eq!(match_block_number(bn_tag_earliest).unwrap(), 0); assert_eq!(match_block_number(bn_tag_latest).unwrap(), 1000); + assert_eq!(match_block_number(bn_tag_safe).unwrap(), 999); + assert_eq!(match_block_number(bn_tag_finalized).unwrap(), 999); assert_eq!(match_block_number(bn_tag_pending).unwrap(), 1001); } } diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 5f9db08e6c..647f8f7985 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -81,6 +81,8 @@ pub mod frontier_backend_client { BlockNumber::Latest => Some(BlockId::Hash(client.info().best_hash)), BlockNumber::Earliest => Some(BlockId::Number(Zero::zero())), BlockNumber::Pending => None, + BlockNumber::Safe => Some(BlockId::Hash(client.info().finalized_hash)), + BlockNumber::Finalized => Some(BlockId::Hash(client.info().finalized_hash)), }) } diff --git a/ts-tests/package.json b/ts-tests/package.json index 8f2a097c7f..100c98a509 100644 --- a/ts-tests/package.json +++ b/ts-tests/package.json @@ -23,7 +23,7 @@ "truffle": "^5.1.62", "ts-node": "^8.10.2", "typescript": "^3.9.6", - "web3": "^1.3.4" + "web3": "^1.8.0-rc.0" }, "devDependencies": { "@types/chai-as-promised": "^7.1.5", diff --git a/ts-tests/tests/test-block-tags.ts b/ts-tests/tests/test-block-tags.ts new file mode 100644 index 0000000000..125d114946 --- /dev/null +++ b/ts-tests/tests/test-block-tags.ts @@ -0,0 +1,30 @@ +import { expect } from "chai"; +import { step } from "mocha-steps"; + +import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, GENESIS_ACCOUNT_BALANCE, EXISTENTIAL_DEPOSIT } from "./config"; +import { createAndFinalizeBlock, describeWithFrontier, customRequest } from "./util"; + +describeWithFrontier("Frontier RPC (BlockNumber tags)", (context) => { + before("Send some transactions across blocks", async function () { + // block #1 finalized + await createAndFinalizeBlock(context.web3); + // block #2 not finalized + await createAndFinalizeBlock(context.web3, false); + }); + + step("`earliest` returns genesis", async function () { + expect((await context.web3.eth.getBlock("earliest")).number).to.equal(0); + }); + + step("`latest` returns `BlockchainInfo::best_hash` number", async function () { + expect((await context.web3.eth.getBlock("latest")).number).to.equal(2); + }); + + step("`finalized` uses `BlockchainInfo::finalized_hash` number", async function () { + expect((await context.web3.eth.getBlock("finalized")).number).to.equal(1); + }); + + step("`safe` is an alias for `finalized` in Polkadot", async function () { + expect((await context.web3.eth.getBlock("safe")).number).to.equal(1); + }); +}); diff --git a/ts-tests/tests/util.ts b/ts-tests/tests/util.ts index 9099dca964..4b944e61f0 100644 --- a/ts-tests/tests/util.ts +++ b/ts-tests/tests/util.ts @@ -41,8 +41,8 @@ export async function customRequest(web3: Web3, method: string, params: any[]) { // Create a block and finalize it. // It will include all previously executed transactions since the last finalized block. -export async function createAndFinalizeBlock(web3: Web3) { - const response = await customRequest(web3, "engine_createBlock", [true, true, null]); +export async function createAndFinalizeBlock(web3: Web3, finalize: boolean = true) { + const response = await customRequest(web3, "engine_createBlock", [true, finalize, null]); if (!response.result) { throw new Error(`Unexpected result: ${JSON.stringify(response)}`); }