Skip to content

Commit

Permalink
Add Pending tag support (polkadot-evm#535)
Browse files Browse the repository at this point in the history
* Add Pending tag support for eth_getCode

* Add test

* Add Pending tag support for additional methods

* Update tests

(cherry picked from commit 9937586)

# Conflicts:
#	client/rpc/Cargo.toml
  • Loading branch information
tgmichel committed Dec 17, 2021
1 parent 48e3d96 commit 9502687
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 32 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ sp-transaction-pool = { version = "4.0.0-dev", git = "https://github.com/puresta
sc-transaction-pool = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sp-storage = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sp-block-builder = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sc-transaction-pool-api = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sc-service = { version = "0.10.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
sc-client-api = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" }
Expand Down
127 changes: 98 additions & 29 deletions client/rpc/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use sc_transaction_pool::{ChainApi, Pool};
use sc_transaction_pool_api::{InPoolTransaction, TransactionPool};
use sha3::{Digest, Keccak256};
use sp_api::{ApiExt, BlockId, Core, HeaderT, ProvideRuntimeApi};
use sp_block_builder::BlockBuilder;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
use sp_runtime::{
traits::{BlakeTwo256, Block as BlockT, NumberFor, One, Saturating, UniqueSaturatedInto, Zero},
Expand Down Expand Up @@ -80,6 +81,7 @@ impl<B: BlockT, C, P, CT, BE, H: ExHashT, A: ChainApi, F> EthApi<B, C, P, CT, BE
where
C: ProvideRuntimeApi<B>,
C::Api: EthereumRuntimeRPCApi<B>,
C::Api: BlockBuilder<B>,
B: BlockT<Hash = H256> + Send + Sync + 'static,
A: ChainApi<Block = B> + 'static,
C: Send + Sync + 'static,
Expand Down Expand Up @@ -274,6 +276,41 @@ fn transaction_build(
transaction
}

fn pending_runtime_api<'a, B: BlockT, C, BE, A: ChainApi>(
client: &'a C,
graph: &'a Pool<A>,
) -> Result<sp_api::ApiRef<'a, C::Api>>
where
C: ProvideRuntimeApi<B> + StorageProvider<B, BE>,
C: HeaderBackend<B> + HeaderMetadata<B, Error = BlockChainError> + 'static,
C::Api: EthereumRuntimeRPCApi<B>,
C::Api: BlockBuilder<B>,
BE: Backend<B> + 'static,
BE::State: StateBackend<BlakeTwo256>,
B: BlockT<Hash = H256> + Send + Sync + 'static,
A: ChainApi<Block = B> + 'static,
C: Send + Sync + 'static,
{
// In case of Pending, we need an overlayed state to query over.
let api = client.runtime_api();
let best = BlockId::Hash(client.info().best_hash);
// Get all transactions in the ready queue.
let xts: Vec<<B as BlockT>::Extrinsic> = graph
.validated_pool()
.ready()
.map(|in_pool_tx| in_pool_tx.data().clone())
.collect::<Vec<<B as BlockT>::Extrinsic>>();
// Manually initialize the overlay.
let header = client.header(best).unwrap().unwrap();
api.initialize_block(&best, &header)
.map_err(|e| internal_err(format!("Runtime api access error: {:?}", e)))?;
// Apply the ready queue to the best block's state.
for xt in xts {
let _ = api.apply_extrinsic(&best, xt);
}
Ok(api)
}

fn filter_range_logs<B: BlockT, C, BE>(
client: &C,
backend: &fc_db::Backend<B>,
Expand Down Expand Up @@ -497,6 +534,7 @@ where
C: ProvideRuntimeApi<B> + StorageProvider<B, BE>,
C: HeaderBackend<B> + HeaderMetadata<B, Error = BlockChainError> + 'static,
C::Api: EthereumRuntimeRPCApi<B>,
C::Api: BlockBuilder<B>,
BE: Backend<B> + 'static,
BE::State: StateBackend<BlakeTwo256>,
B: BlockT<Hash = H256> + Send + Sync + 'static,
Expand Down Expand Up @@ -595,10 +633,18 @@ where
}

fn balance(&self, address: H160, number: Option<BlockNumber>) -> Result<U256> {
if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
let number = number.unwrap_or(BlockNumber::Latest);
if number == BlockNumber::Pending {
let api = pending_runtime_api(self.client.as_ref(), self.graph.as_ref())?;
return Ok(api
.account_basic(&BlockId::Hash(self.client.info().best_hash), address)
.map_err(|err| internal_err(format!("fetch runtime chain id failed: {:?}", err)))?
.balance
.into());
} else if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
self.client.as_ref(),
self.backend.as_ref(),
number,
Some(number),
) {
return Ok(self
.client
Expand All @@ -607,15 +653,22 @@ where
.map_err(|err| internal_err(format!("fetch runtime chain id failed: {:?}", err)))?
.balance
.into());
} else {
Ok(U256::zero())
}
Ok(U256::zero())
}

fn storage_at(&self, address: H160, index: U256, number: Option<BlockNumber>) -> Result<H256> {
if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
let number = number.unwrap_or(BlockNumber::Latest);
if number == BlockNumber::Pending {
let api = pending_runtime_api(self.client.as_ref(), self.graph.as_ref())?;
return Ok(api
.storage_at(&BlockId::Hash(self.client.info().best_hash), address, index)
.unwrap_or_default());
} else if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
self.client.as_ref(),
self.backend.as_ref(),
number,
Some(number),
) {
let schema = frontier_backend_client::onchain_storage_schema::<B, C, BE>(
self.client.as_ref(),
Expand All @@ -628,8 +681,9 @@ where
.unwrap_or(&self.overrides.fallback)
.storage_at(&id, address, index)
.unwrap_or_default());
} else {
Ok(H256::default())
}
Ok(H256::default())
}

fn block_by_hash(&self, hash: H256, full: bool) -> Result<Option<RichBlock>> {
Expand Down Expand Up @@ -823,10 +877,17 @@ where
}

fn code_at(&self, address: H160, number: Option<BlockNumber>) -> Result<Bytes> {
if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
let number = number.unwrap_or(BlockNumber::Latest);
if number == BlockNumber::Pending {
let api = pending_runtime_api(self.client.as_ref(), self.graph.as_ref())?;
return Ok(api
.account_code_at(&BlockId::Hash(self.client.info().best_hash), address)
.unwrap_or(vec![])
.into());
} else if let Ok(Some(id)) = frontier_backend_client::native_block_id::<B, C>(
self.client.as_ref(),
self.backend.as_ref(),
number,
Some(number),
) {
let schema = frontier_backend_client::onchain_storage_schema::<B, C, BE>(
self.client.as_ref(),
Expand All @@ -841,8 +902,9 @@ where
.account_code_at(&id, address)
.unwrap_or(vec![])
.into());
} else {
Ok(Bytes(vec![]))
}
Ok(Bytes(vec![]))
}

fn send_transaction(&self, request: TransactionRequest) -> BoxFuture<Result<H256>> {
Expand Down Expand Up @@ -999,11 +1061,7 @@ where
)
}

fn call(&self, request: CallRequest, _: Option<BlockNumber>) -> Result<Bytes> {
// TODO required support for requests with block parameter instead of defaulting to latest.
// That's the main reason for creating a new runtime api version for the `call` and `create` methods.
let hash = self.client.info().best_hash;

fn call(&self, request: CallRequest, number: Option<BlockNumber>) -> Result<Bytes> {
let CallRequest {
from,
to,
Expand All @@ -1025,14 +1083,26 @@ where
)
};

let api = self.client.runtime_api();
let (id, api) = match frontier_backend_client::native_block_id::<B, C>(
self.client.as_ref(),
self.backend.as_ref(),
number,
)? {
Some(id) => (id, self.client.runtime_api()),
None => {
// Not mapped in the db, assume pending.
let id = BlockId::Hash(self.client.info().best_hash);
let api = pending_runtime_api(self.client.as_ref(), self.graph.as_ref())?;
(id, api)
}
};

// use given gas limit or query current block's limit
let gas_limit = match gas {
Some(amount) => amount,
None => {
let block = api
.current_block(&BlockId::Hash(hash))
.current_block(&id)
.map_err(|err| internal_err(format!("runtime error: {:?}", err)))?;
if let Some(block) = block {
block.header.gas_limit
Expand All @@ -1045,21 +1115,20 @@ where
};
let data = data.map(|d| d.0).unwrap_or_default();

let api_version = if let Ok(Some(api_version)) =
api.api_version::<dyn EthereumRuntimeRPCApi<B>>(&BlockId::Hash(hash))
{
api_version
} else {
return Err(internal_err(format!(
"failed to retrieve Runtime Api version"
)));
};
let api_version =
if let Ok(Some(api_version)) = api.api_version::<dyn EthereumRuntimeRPCApi<B>>(&id) {
api_version
} else {
return Err(internal_err(format!(
"failed to retrieve Runtime Api version"
)));
};
match to {
Some(to) => {
if api_version == 1 {
#[allow(deprecated)]
let info = api.call_before_version_2(
&BlockId::Hash(hash),
&id,
from.unwrap_or_default(),
to,
data,
Expand All @@ -1077,7 +1146,7 @@ where
} else if api_version == 2 {
let info = api
.call(
&BlockId::Hash(hash),
&id,
from.unwrap_or_default(),
to,
data,
Expand All @@ -1103,7 +1172,7 @@ where
if api_version == 1 {
#[allow(deprecated)]
let info = api.create_before_version_2(
&BlockId::Hash(hash),
&id,
from.unwrap_or_default(),
data,
value.unwrap_or_default(),
Expand All @@ -1120,7 +1189,7 @@ where
} else if api_version == 2 {
let info = api
.create(
&BlockId::Hash(hash),
&id,
from.unwrap_or_default(),
data,
value.unwrap_or_default(),
Expand Down
8 changes: 6 additions & 2 deletions ts-tests/tests/test-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ describeWithFrontier("Frontier RPC (Balance)", (context) => {
gas: "0x100000",
}, GENESIS_ACCOUNT_PRIVATE_KEY);
await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
const expectedGenesisBalance = "340282366920938463463374586431768210443";
const expectedTestBalance = "12";
expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT, "pending")).to.equal(expectedGenesisBalance);
expect(await context.web3.eth.getBalance(TEST_ACCOUNT, "pending")).to.equal(expectedTestBalance);
await createAndFinalizeBlock(context.web3);
// 340282366920938463463374607431768210955 - (21000 * 1000000000) + 512;
expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal("340282366920938463463374586431768210443");
expect(await context.web3.eth.getBalance(TEST_ACCOUNT)).to.equal("12");
expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal(expectedGenesisBalance);
expect(await context.web3.eth.getBalance(TEST_ACCOUNT)).to.equal(expectedTestBalance);
});
});
15 changes: 14 additions & 1 deletion ts-tests/tests/test-contract-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => {
);

await customRequest(context.web3, "eth_sendRawTransaction", [tx1.rawTransaction]);

let getStoragePending = await customRequest(context.web3, "eth_getStorageAt", [
contractAddress,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"pending",
]);

const expectedStorage = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";

expect(getStoragePending.result).to.be.eq(
expectedStorage
);

await createAndFinalizeBlock(context.web3);
let receip1 = await context.web3.eth.getTransactionReceipt(tx1.transactionHash);

Expand All @@ -74,7 +87,7 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => {
]);

expect(getStorage1.result).to.be.eq(
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
expectedStorage
);
});
});
7 changes: 7 additions & 0 deletions ts-tests/tests/test-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => {
result: "0x",
});

// Verify the contract is in the pending state
expect(await customRequest(context.web3, "eth_getCode", [FIRST_CONTRACT_ADDRESS, "pending"])).to.deep.equal({
id: 1,
jsonrpc: "2.0",
result: TEST_CONTRACT_DEPLOYED_BYTECODE,
});

// Verify the contract is stored after the block is produced
await createAndFinalizeBlock(context.web3);
expect(await customRequest(context.web3, "eth_getCode", [FIRST_CONTRACT_ADDRESS])).to.deep.equal({
Expand Down

0 comments on commit 9502687

Please sign in to comment.