|
17 | 17 | use std::sync::Arc; |
18 | 18 | use futures::{IntoFuture, Future}; |
19 | 19 | use runtime_primitives::generic::BlockId; |
20 | | -use runtime_primitives::traits::Block as BlockT; |
| 20 | +use runtime_primitives::traits::{Block as BlockT, Header as HeaderT}; |
21 | 21 | use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor}; |
22 | | -use state_machine::backend::InMemory as InMemoryStateBackend; |
23 | 22 |
|
24 | 23 | use backend; |
25 | 24 | use blockchain::Backend as ChainBackend; |
@@ -49,6 +48,11 @@ pub trait CallExecutor<B: BlockT> { |
49 | 48 | /// |
50 | 49 | /// No changes are made. |
51 | 50 | fn call_at_state<S: state_machine::Backend>(&self, state: &S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, S::Transaction), error::Error>; |
| 51 | + |
| 52 | + /// Execute a call to a contract on top of given state, gathering execution proof. |
| 53 | + /// |
| 54 | + /// No changes are made. |
| 55 | + fn prove_at_state<S: state_machine::Backend>(&self, state: S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>; |
52 | 56 | } |
53 | 57 |
|
54 | 58 | /// Call executor that executes methods locally, querying all required |
@@ -105,6 +109,18 @@ impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E> |
105 | 109 | call_data, |
106 | 110 | ).map_err(Into::into) |
107 | 111 | } |
| 112 | + |
| 113 | + fn prove_at_state<S: state_machine::Backend>(&self, state: S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> { |
| 114 | + state_machine::prove( |
| 115 | + state, |
| 116 | + changes, |
| 117 | + &self.executor, |
| 118 | + method, |
| 119 | + call_data, |
| 120 | + ) |
| 121 | + .map(|(result, proof, _)| (result, proof)) |
| 122 | + .map_err(Into::into) |
| 123 | + } |
108 | 124 | } |
109 | 125 |
|
110 | 126 | impl<B, F> RemoteCallExecutor<B, F> { |
@@ -140,69 +156,70 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F> |
140 | 156 | fn call_at_state<S: state_machine::Backend>(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec<u8>, S::Transaction)> { |
141 | 157 | Err(error::ErrorKind::NotAvailableOnLightClient.into()) |
142 | 158 | } |
| 159 | + |
| 160 | + fn prove_at_state<S: state_machine::Backend>(&self, _state: S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> { |
| 161 | + Err(error::ErrorKind::NotAvailableOnLightClient.into()) |
| 162 | + } |
143 | 163 | } |
144 | 164 |
|
145 | | -/// Check remote execution proof. |
146 | | -pub fn check_execution_proof<B, E, Block>(backend: &B, executor: &E, request: &RemoteCallRequest<Block::Hash>, remote_proof: (Vec<u8>, Vec<Vec<u8>>)) -> Result<CallResult, error::Error> |
| 165 | +/// Check remote execution proof using given backend. |
| 166 | +pub fn check_execution_proof<B, E, Block>(backend: &B, executor: &E, request: &RemoteCallRequest<Block::Hash>, remote_proof: Vec<Vec<u8>>) -> Result<CallResult, error::Error> |
147 | 167 | where |
148 | 168 | B: backend::RemoteBackend<Block>, |
149 | 169 | E: CodeExecutor, |
150 | 170 | Block: BlockT, |
| 171 | + <<Block as BlockT>::Header as HeaderT>::Hash: Into<[u8; 32]>, // TODO: remove when patricia_trie generic. |
151 | 172 | error::Error: From<<<B as backend::Backend<Block>>::State as StateBackend>::Error>, |
152 | 173 | { |
153 | | - use runtime_primitives::traits::{Header, Hashing, HashingFor}; |
154 | | - |
155 | | - let (remote_result, remote_proof) = remote_proof; |
156 | | - |
157 | | - let remote_state = state_from_execution_proof(remote_proof); |
158 | | - let remote_state_root = HashingFor::<Block>::trie_root(remote_state.pairs().into_iter()); |
159 | | - |
160 | 174 | let local_header = backend.blockchain().header(BlockId::Hash(request.block))?; |
161 | | - let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", request.block)))?; |
| 175 | + let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", request.block)))?; |
162 | 176 | let local_state_root = local_header.state_root().clone(); |
| 177 | + do_check_execution_proof(local_state_root, executor, request, remote_proof) |
| 178 | +} |
163 | 179 |
|
164 | | - if remote_state_root != local_state_root { |
165 | | - return Err(error::ErrorKind::InvalidExecutionProof.into()); |
166 | | - } |
167 | | - |
| 180 | +/// Check remote execution proof using given state root. |
| 181 | +fn do_check_execution_proof<E, H>(local_state_root: H, executor: &E, request: &RemoteCallRequest<H>, remote_proof: Vec<Vec<u8>>) -> Result<CallResult, error::Error> |
| 182 | + where |
| 183 | + E: CodeExecutor, |
| 184 | + H: Into<[u8; 32]>, // TODO: remove when patricia_trie generic. |
| 185 | +{ |
168 | 186 | let mut changes = OverlayedChanges::default(); |
169 | | - let (local_result, _) = state_machine::execute( |
170 | | - &remote_state, |
| 187 | + let (local_result, _) = state_machine::proof_check( |
| 188 | + local_state_root.into(), |
| 189 | + remote_proof, |
171 | 190 | &mut changes, |
172 | 191 | executor, |
173 | 192 | &request.method, |
174 | | - &request.call_data, |
175 | | - )?; |
176 | | - |
177 | | - if local_result != remote_result { |
178 | | - return Err(error::ErrorKind::InvalidExecutionProof.into()); |
179 | | - } |
| 193 | + &request.call_data)?; |
180 | 194 |
|
181 | 195 | Ok(CallResult { return_data: local_result, changes }) |
182 | 196 | } |
183 | 197 |
|
184 | | -/// Convert state to execution proof. Proof is simple the whole state (temporary). |
185 | | -// TODO [light]: this method must be removed after trie-based proofs are landed. |
186 | | -pub fn state_to_execution_proof<B: state_machine::Backend>(state: &B) -> Vec<Vec<u8>> { |
187 | | - state.pairs().into_iter() |
188 | | - .flat_map(|(k, v)| ::std::iter::once(k).chain(::std::iter::once(v))) |
189 | | - .collect() |
190 | | -} |
191 | | - |
192 | | -/// Convert execution proof to in-memory state for check. Reverse function for state_to_execution_proof. |
193 | | -// TODO [light]: this method must be removed after trie-based proofs are landed. |
194 | | -fn state_from_execution_proof(proof: Vec<Vec<u8>>) -> InMemoryStateBackend { |
195 | | - let mut changes = Vec::new(); |
196 | | - let mut proof_iter = proof.into_iter(); |
197 | | - loop { |
198 | | - let key = proof_iter.next(); |
199 | | - let value = proof_iter.next(); |
200 | | - if let (Some(key), Some(value)) = (key, value) { |
201 | | - changes.push((key, Some(value))); |
202 | | - } else { |
203 | | - break; |
204 | | - } |
| 198 | +#[cfg(test)] |
| 199 | +mod tests { |
| 200 | + use runtime_primitives::generic::BlockId; |
| 201 | + use state_machine::Backend; |
| 202 | + use test_client; |
| 203 | + use light::RemoteCallRequest; |
| 204 | + use super::do_check_execution_proof; |
| 205 | + |
| 206 | + #[test] |
| 207 | + fn execution_proof_is_generated_and_checked() { |
| 208 | + // prepare remote client |
| 209 | + let remote_client = test_client::new(); |
| 210 | + let remote_block_id = BlockId::Number(0); |
| 211 | + let remote_block_storage_root = remote_client.state_at(&remote_block_id) |
| 212 | + .unwrap().storage_root(::std::iter::empty()).0; |
| 213 | + |
| 214 | + // 'fetch' execution proof from remote node |
| 215 | + let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "authorities", &[]).unwrap().1; |
| 216 | + |
| 217 | + // check remote execution proof locally |
| 218 | + let local_executor = test_client::NativeExecutor::new(); |
| 219 | + do_check_execution_proof(remote_block_storage_root, &local_executor, &RemoteCallRequest { |
| 220 | + block: Default::default(), |
| 221 | + method: "authorities".into(), |
| 222 | + call_data: vec![], |
| 223 | + }, remote_execution_proof).unwrap(); |
205 | 224 | } |
206 | | - |
207 | | - InMemoryStateBackend::default().update(changes) |
208 | 225 | } |
0 commit comments