Support 'pending' block in RPC (#1007)
* Support `pending` block in RPC * Forward calls from miner to client in case no pending block is available
This commit is contained in:
parent
ea669ac6b6
commit
8f7624f5cb
@ -702,6 +702,10 @@ impl<V> BlockChainClient for Client<V> where V: Verifier {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn last_hashes(&self) -> LastHashes {
|
||||||
|
self.build_last_hashes(self.chain.best_block_hash())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MayPanic for Client {
|
impl MayPanic for Client {
|
||||||
|
@ -25,7 +25,8 @@ pub use self::client::*;
|
|||||||
pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig};
|
pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig};
|
||||||
pub use self::ids::{BlockId, TransactionId, UncleId};
|
pub use self::ids::{BlockId, TransactionId, UncleId};
|
||||||
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
|
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
|
||||||
pub use executive::Executed;
|
pub use executive::{Executed, Executive, TransactOptions};
|
||||||
|
pub use env_info::{LastHashes, EnvInfo};
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use util::bytes::Bytes;
|
use util::bytes::Bytes;
|
||||||
@ -132,5 +133,8 @@ pub trait BlockChainClient : Sync + Send {
|
|||||||
|
|
||||||
/// Executes a function providing it with a reference to an engine.
|
/// Executes a function providing it with a reference to an engine.
|
||||||
fn engine(&self) -> &Engine;
|
fn engine(&self) -> &Engine;
|
||||||
|
|
||||||
|
/// Get last hashes starting from best block.
|
||||||
|
fn last_hashes(&self) -> LastHashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder};
|
|||||||
use util::*;
|
use util::*;
|
||||||
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action};
|
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action};
|
||||||
use blockchain::TreeRoute;
|
use blockchain::TreeRoute;
|
||||||
use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId};
|
use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, LastHashes};
|
||||||
use header::{Header as BlockHeader, BlockNumber};
|
use header::{Header as BlockHeader, BlockNumber};
|
||||||
use filter::Filter;
|
use filter::Filter;
|
||||||
use log_entry::LocalizedLogEntry;
|
use log_entry::LocalizedLogEntry;
|
||||||
@ -268,6 +268,10 @@ impl BlockChainClient for TestBlockChainClient {
|
|||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn last_hashes(&self) -> LastHashes {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare_sealing(&self, _author: Address, _gas_floor_target: U256, _extra_data: Bytes, _transactions: Vec<SignedTransaction>) -> (Option<ClosedBlock>, HashSet<H256>) {
|
fn prepare_sealing(&self, _author: Address, _gas_floor_target: U256, _extra_data: Bytes, _transactions: Vec<SignedTransaction>) -> (Option<ClosedBlock>, HashSet<H256>) {
|
||||||
(None, HashSet::new())
|
(None, HashSet::new())
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ impl<'a> Executive<'a> {
|
|||||||
Externalities::new(self.state, self.info, self.engine, self.depth, origin_info, substate, output, tracer)
|
Externalities::new(self.state, self.info, self.engine, self.depth, origin_info, substate, output, tracer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This funtion should be used to execute transaction.
|
/// This function should be used to execute transaction.
|
||||||
pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, Error> {
|
pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, Error> {
|
||||||
let check = options.check_nonce;
|
let check = options.check_nonce;
|
||||||
match options.tracing {
|
match options.tracing {
|
||||||
@ -126,6 +126,7 @@ impl<'a> Executive<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute transaction/call with tracing enabled
|
||||||
pub fn transact_with_tracer<T>(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result<Executed, Error> where T: Tracer {
|
pub fn transact_with_tracer<T>(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result<Executed, Error> where T: Tracer {
|
||||||
let sender = try!(t.sender());
|
let sender = try!(t.sender());
|
||||||
let nonce = self.state.nonce(&sender);
|
let nonce = self.state.nonce(&sender);
|
||||||
|
@ -61,7 +61,7 @@ pub use miner::{Miner};
|
|||||||
pub use external::{ExternalMiner, ExternalMinerService};
|
pub use external::{ExternalMiner, ExternalMinerService};
|
||||||
|
|
||||||
use util::{H256, U256, Address, Bytes};
|
use util::{H256, U256, Address, Bytes};
|
||||||
use ethcore::client::{BlockChainClient};
|
use ethcore::client::{BlockChainClient, Executed};
|
||||||
use ethcore::block::{ClosedBlock};
|
use ethcore::block::{ClosedBlock};
|
||||||
use ethcore::error::{Error};
|
use ethcore::error::{Error};
|
||||||
use ethcore::transaction::SignedTransaction;
|
use ethcore::transaction::SignedTransaction;
|
||||||
@ -145,6 +145,21 @@ pub trait MinerService : Send + Sync {
|
|||||||
|
|
||||||
/// Suggested gas limit.
|
/// Suggested gas limit.
|
||||||
fn sensible_gas_limit(&self) -> U256 { x!(21000) }
|
fn sensible_gas_limit(&self) -> U256 { x!(21000) }
|
||||||
|
|
||||||
|
/// Account balance
|
||||||
|
fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256;
|
||||||
|
|
||||||
|
/// Call into contract code using pending state.
|
||||||
|
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result<Executed, Error>;
|
||||||
|
|
||||||
|
/// Get storage value in pending state.
|
||||||
|
fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256;
|
||||||
|
|
||||||
|
/// Get account nonce in pending state.
|
||||||
|
fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256;
|
||||||
|
|
||||||
|
/// Get contract code in pending state.
|
||||||
|
fn code(&self, chain: &BlockChainClient, address: &Address) -> Option<Bytes>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mining status
|
/// Mining status
|
||||||
|
@ -22,6 +22,7 @@ use ethcore::views::{BlockView, HeaderView};
|
|||||||
use ethcore::client::{BlockChainClient, BlockId};
|
use ethcore::client::{BlockChainClient, BlockId};
|
||||||
use ethcore::block::{ClosedBlock, IsBlock};
|
use ethcore::block::{ClosedBlock, IsBlock};
|
||||||
use ethcore::error::*;
|
use ethcore::error::*;
|
||||||
|
use ethcore::client::{Executive, Executed, EnvInfo, TransactOptions};
|
||||||
use ethcore::transaction::SignedTransaction;
|
use ethcore::transaction::SignedTransaction;
|
||||||
use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult};
|
use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult};
|
||||||
|
|
||||||
@ -195,6 +196,58 @@ impl MinerService for Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call(&self, chain: &BlockChainClient, t: &SignedTransaction) -> Result<Executed, Error> {
|
||||||
|
let sealing_work = self.sealing_work.lock().unwrap();
|
||||||
|
match sealing_work.peek_last_ref() {
|
||||||
|
Some(work) => {
|
||||||
|
let block = work.block();
|
||||||
|
let header = block.header();
|
||||||
|
let last_hashes = chain.last_hashes();
|
||||||
|
let env_info = EnvInfo {
|
||||||
|
number: header.number(),
|
||||||
|
author: header.author().clone(),
|
||||||
|
timestamp: header.timestamp(),
|
||||||
|
difficulty: header.difficulty().clone(),
|
||||||
|
last_hashes: last_hashes,
|
||||||
|
gas_used: U256::zero(),
|
||||||
|
gas_limit: U256::max_value(),
|
||||||
|
};
|
||||||
|
// that's just a copy of the state.
|
||||||
|
let mut state = block.state().clone();
|
||||||
|
let sender = try!(t.sender());
|
||||||
|
let balance = state.balance(&sender);
|
||||||
|
// give the sender max balance
|
||||||
|
state.sub_balance(&sender, &balance);
|
||||||
|
state.add_balance(&sender, &U256::max_value());
|
||||||
|
let options = TransactOptions { tracing: false, check_nonce: false };
|
||||||
|
Executive::new(&mut state, &env_info, chain.engine()).transact(t, options)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
chain.call(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn balance(&self, chain: &BlockChainClient, address: &Address) -> U256 {
|
||||||
|
let sealing_work = self.sealing_work.lock().unwrap();
|
||||||
|
sealing_work.peek_last_ref().map_or_else(|| chain.balance(address), |b| b.block().fields().state.balance(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_at(&self, chain: &BlockChainClient, address: &Address, position: &H256) -> H256 {
|
||||||
|
let sealing_work = self.sealing_work.lock().unwrap();
|
||||||
|
sealing_work.peek_last_ref().map_or_else(|| chain.storage_at(address, position), |b| b.block().fields().state.storage_at(address, position))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonce(&self, chain: &BlockChainClient, address: &Address) -> U256 {
|
||||||
|
let sealing_work = self.sealing_work.lock().unwrap();
|
||||||
|
sealing_work.peek_last_ref().map_or_else(|| chain.nonce(address), |b| b.block().fields().state.nonce(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code(&self, chain: &BlockChainClient, address: &Address) -> Option<Bytes> {
|
||||||
|
let sealing_work = self.sealing_work.lock().unwrap();
|
||||||
|
sealing_work.peek_last_ref().map_or_else(|| chain.code(address), |b| b.block().fields().state.code(address))
|
||||||
|
}
|
||||||
|
|
||||||
fn set_author(&self, author: Address) {
|
fn set_author(&self, author: Address) {
|
||||||
*self.author.write().unwrap() = author;
|
*self.author.write().unwrap() = author;
|
||||||
}
|
}
|
||||||
|
@ -200,17 +200,17 @@ fn params_len(params: &Params) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_params_discard_second<F>(params: Params) -> Result<(F,), Error> where F: serde::de::Deserialize {
|
fn from_params_default_second<F>(params: Params) -> Result<(F, BlockNumber, ), Error> where F: serde::de::Deserialize {
|
||||||
match params_len(¶ms) {
|
match params_len(¶ms) {
|
||||||
1 => from_params::<(F, )>(params),
|
1 => from_params::<(F, )>(params).map(|(f,)| (f, BlockNumber::Latest)),
|
||||||
_ => from_params::<(F, BlockNumber)>(params).map(|(f, _block_number)| (f, )),
|
_ => from_params::<(F, BlockNumber)>(params),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_params_discard_third<F1, F2>(params: Params) -> Result<(F1, F2,), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize {
|
fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize {
|
||||||
match params_len(¶ms) {
|
match params_len(¶ms) {
|
||||||
2 => from_params::<(F1, F2, )>(params),
|
2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)),
|
||||||
_ => from_params::<(F1, F2, BlockNumber)>(params).map(|(f1, f2, _block_number)| (f1, f2, )),
|
_ => from_params::<(F1, F2, BlockNumber)>(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,18 +293,30 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn balance(&self, params: Params) -> Result<Value, Error> {
|
fn balance(&self, params: Params) -> Result<Value, Error> {
|
||||||
from_params_discard_second(params).and_then(|(address, )|
|
from_params_default_second(params)
|
||||||
to_value(&take_weak!(self.client).balance(&address)))
|
.and_then(|(address, block_number,)| match block_number {
|
||||||
|
BlockNumber::Latest => to_value(&take_weak!(self.client).balance(&address)),
|
||||||
|
BlockNumber::Pending => to_value(&take_weak!(self.miner).balance(take_weak!(self.client).deref(), &address)),
|
||||||
|
_ => Err(Error::invalid_params()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage_at(&self, params: Params) -> Result<Value, Error> {
|
fn storage_at(&self, params: Params) -> Result<Value, Error> {
|
||||||
from_params_discard_third::<Address, U256>(params).and_then(|(address, position, )|
|
from_params_default_third::<Address, U256>(params)
|
||||||
to_value(&U256::from(take_weak!(self.client).storage_at(&address, &H256::from(position)))))
|
.and_then(|(address, position, block_number,)| match block_number {
|
||||||
|
BlockNumber::Pending => to_value(&U256::from(take_weak!(self.miner).storage_at(take_weak!(self.client).deref(), &address, &H256::from(position)))),
|
||||||
|
BlockNumber::Latest => to_value(&U256::from(take_weak!(self.client).storage_at(&address, &H256::from(position)))),
|
||||||
|
_ => Err(Error::invalid_params()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_count(&self, params: Params) -> Result<Value, Error> {
|
fn transaction_count(&self, params: Params) -> Result<Value, Error> {
|
||||||
from_params_discard_second(params).and_then(|(address, )|
|
from_params_default_second(params)
|
||||||
to_value(&take_weak!(self.client).nonce(&address)))
|
.and_then(|(address, block_number,)| match block_number {
|
||||||
|
BlockNumber::Pending => to_value(&take_weak!(self.miner).nonce(take_weak!(self.client).deref(), &address)),
|
||||||
|
BlockNumber::Latest => to_value(&take_weak!(self.client).nonce(&address)),
|
||||||
|
_ => Err(Error::invalid_params()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_transaction_count_by_hash(&self, params: Params) -> Result<Value, Error> {
|
fn block_transaction_count_by_hash(&self, params: Params) -> Result<Value, Error> {
|
||||||
@ -341,10 +353,13 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do not ignore block number param
|
|
||||||
fn code_at(&self, params: Params) -> Result<Value, Error> {
|
fn code_at(&self, params: Params) -> Result<Value, Error> {
|
||||||
from_params_discard_second(params).and_then(|(address, )|
|
from_params_default_second(params)
|
||||||
to_value(&take_weak!(self.client).code(&address).map_or_else(Bytes::default, Bytes::new)))
|
.and_then(|(address, block_number,)| match block_number {
|
||||||
|
BlockNumber::Pending => to_value(&take_weak!(self.miner).code(take_weak!(self.client).deref(), &address).map_or_else(Bytes::default, Bytes::new)),
|
||||||
|
BlockNumber::Latest => to_value(&take_weak!(self.client).code(&address).map_or_else(Bytes::default, Bytes::new)),
|
||||||
|
_ => Err(Error::invalid_params()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_by_hash(&self, params: Params) -> Result<Value, Error> {
|
fn block_by_hash(&self, params: Params) -> Result<Value, Error> {
|
||||||
@ -502,20 +517,28 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
|
|||||||
|
|
||||||
fn call(&self, params: Params) -> Result<Value, Error> {
|
fn call(&self, params: Params) -> Result<Value, Error> {
|
||||||
trace!(target: "jsonrpc", "call: {:?}", params);
|
trace!(target: "jsonrpc", "call: {:?}", params);
|
||||||
from_params_discard_second(params).and_then(|(request, )| {
|
from_params_default_second(params)
|
||||||
|
.and_then(|(request, block_number,)| {
|
||||||
let signed = try!(self.sign_call(request));
|
let signed = try!(self.sign_call(request));
|
||||||
let client = take_weak!(self.client);
|
let r = match block_number {
|
||||||
let output = client.call(&signed).map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]));
|
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed),
|
||||||
to_value(&output)
|
BlockNumber::Latest => take_weak!(self.client).call(&signed),
|
||||||
|
_ => panic!("{:?}", block_number),
|
||||||
|
};
|
||||||
|
to_value(&r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![])))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_gas(&self, params: Params) -> Result<Value, Error> {
|
fn estimate_gas(&self, params: Params) -> Result<Value, Error> {
|
||||||
from_params_discard_second(params).and_then(|(request, )| {
|
from_params_default_second(params)
|
||||||
|
.and_then(|(request, block_number,)| {
|
||||||
let signed = try!(self.sign_call(request));
|
let signed = try!(self.sign_call(request));
|
||||||
let client = take_weak!(self.client);
|
let r = match block_number {
|
||||||
let used = client.call(&signed).map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0));
|
BlockNumber::Pending => take_weak!(self.miner).call(take_weak!(self.client).deref(), &signed),
|
||||||
to_value(&used)
|
BlockNumber::Latest => take_weak!(self.client).call(&signed),
|
||||||
|
_ => return Err(Error::invalid_params()),
|
||||||
|
};
|
||||||
|
to_value(&r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,22 @@ fn rpc_eth_balance() {
|
|||||||
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
|
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ignore] //TODO: propert test
|
||||||
|
#[test]
|
||||||
|
fn rpc_eth_balance_pending() {
|
||||||
|
let tester = EthTester::default();
|
||||||
|
|
||||||
|
let request = r#"{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getBalance",
|
||||||
|
"params": ["0x0000000000000000000000000000000000000001", "latest"],
|
||||||
|
"id": 1
|
||||||
|
}"#;
|
||||||
|
let response = r#"{"jsonrpc":"2.0","result":"0x","id":1}"#;
|
||||||
|
|
||||||
|
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_eth_storage_at() {
|
fn rpc_eth_storage_at() {
|
||||||
let tester = EthTester::default();
|
let tester = EthTester::default();
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
//! Test implementation of miner service.
|
//! Test implementation of miner service.
|
||||||
|
|
||||||
use util::{Address, H256, Bytes, U256, FixedHash};
|
use util::{Address, H256, Bytes, U256, FixedHash, Uint};
|
||||||
use util::standard::*;
|
use util::standard::*;
|
||||||
use ethcore::error::Error;
|
use ethcore::error::Error;
|
||||||
use ethcore::client::BlockChainClient;
|
use ethcore::client::{BlockChainClient, Executed};
|
||||||
use ethcore::block::ClosedBlock;
|
use ethcore::block::{ClosedBlock, IsBlock};
|
||||||
use ethcore::transaction::SignedTransaction;
|
use ethcore::transaction::SignedTransaction;
|
||||||
use ethminer::{MinerService, MinerStatus, AccountDetails, TransactionImportResult};
|
use ethminer::{MinerService, MinerStatus, AccountDetails, TransactionImportResult};
|
||||||
|
|
||||||
@ -174,4 +174,25 @@ impl MinerService for TestMinerService {
|
|||||||
fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec<Bytes>) -> Result<(), Error> {
|
fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec<Bytes>) -> Result<(), Error> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn balance(&self, _chain: &BlockChainClient, address: &Address) -> U256 {
|
||||||
|
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.balance(address).clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, _chain: &BlockChainClient, _t: &SignedTransaction) -> Result<Executed, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_at(&self, _chain: &BlockChainClient, address: &Address, position: &H256) -> H256 {
|
||||||
|
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(H256::default, |b| b.block().fields().state.storage_at(address, position).clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonce(&self, _chain: &BlockChainClient, address: &Address) -> U256 {
|
||||||
|
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.nonce(address).clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code(&self, _chain: &BlockChainClient, address: &Address) -> Option<Bytes> {
|
||||||
|
self.latest_closed_block.lock().unwrap().as_ref().map_or(None, |b| b.block().fields().state.code(address).clone())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user