cfx_vm_interpreter/interpreter/
shared_cache.rs

1// Copyright 2015-2020 Parity Technologies (UK) Ltd.
2// This file is part of Open Ethereum.
3
4// Open Ethereum is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Open Ethereum is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Open Ethereum.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::instructions::{self, Instruction};
18use bit_set::BitSet;
19use cfx_types::H256;
20use keccak_hash::KECCAK_EMPTY;
21use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
22use memory_cache::MemoryLruCache;
23use parking_lot::Mutex;
24use std::sync::Arc;
25
26#[cfg(test)]
27use rustc_hex::FromHex;
28
29const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
30
31/// Stub for a sharing `BitSet` data in cache (reference counted)
32/// and implementing MallocSizeOf on it.
33#[derive(Clone)]
34struct Bits(Arc<BitSet>);
35
36impl MallocSizeOf for Bits {
37    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
38        // dealing in bits here
39        self.0.capacity() * 8
40    }
41}
42
43#[derive(Clone)]
44struct CacheItem {
45    jump_destination: Bits,
46    sub_entrypoint: Bits,
47}
48
49impl MallocSizeOf for CacheItem {
50    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
51        self.jump_destination.size_of(ops) + self.sub_entrypoint.size_of(ops)
52    }
53}
54
55/// Global cache for EVM interpreter
56pub struct SharedCache<const CANCUN: bool> {
57    jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
58}
59
60impl<const CANCUN: bool> SharedCache<CANCUN> {
61    /// Create a jump destinations cache with a maximum size in bytes
62    /// to cache.
63    pub fn new(max_size: usize) -> Self {
64        SharedCache {
65            jump_destinations: Mutex::new(MemoryLruCache::new(max_size)),
66        }
67    }
68
69    /// Get jump destinations bitmap for a contract.
70    pub fn jump_and_sub_destinations(
71        &self, code_hash: &H256, code: &[u8],
72    ) -> (Arc<BitSet>, Arc<BitSet>) {
73        if code_hash == &KECCAK_EMPTY {
74            let cache_item = Self::find_jump_and_sub_destinations(code);
75            return (
76                cache_item.jump_destination.0,
77                cache_item.sub_entrypoint.0,
78            );
79        }
80
81        if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
82            return (d.jump_destination.0.clone(), d.sub_entrypoint.0.clone());
83        }
84
85        let d = Self::find_jump_and_sub_destinations(code);
86        self.jump_destinations.lock().insert(*code_hash, d.clone());
87
88        (d.jump_destination.0, d.sub_entrypoint.0)
89    }
90
91    fn find_jump_and_sub_destinations(code: &[u8]) -> CacheItem {
92        let mut jump_dests = BitSet::with_capacity(code.len());
93        let mut sub_entrypoints = BitSet::with_capacity(code.len());
94        let mut position = 0;
95
96        while position < code.len() {
97            let instruction = Instruction::from_u8(code[position]);
98
99            if let Some(instruction) = instruction {
100                match instruction {
101                    instructions::JUMPDEST => {
102                        jump_dests.insert(position);
103                    }
104                    instructions::BEGINSUB_TLOAD if !CANCUN => {
105                        sub_entrypoints.insert(position);
106                    }
107                    _ => {
108                        if let Some(push_bytes) = instruction.push_bytes() {
109                            position += push_bytes;
110                        }
111                    }
112                }
113            }
114            position += 1;
115        }
116
117        jump_dests.shrink_to_fit();
118        CacheItem {
119            jump_destination: Bits(Arc::new(jump_dests)),
120            sub_entrypoint: Bits(Arc::new(sub_entrypoints)),
121        }
122    }
123}
124
125impl<const CANCUN: bool> Default for SharedCache<CANCUN> {
126    fn default() -> Self { SharedCache::new(DEFAULT_CACHE_SIZE) }
127}
128
129#[test]
130fn test_find_jump_destinations() {
131    // given
132
133    // 0000 7F   PUSH32
134    // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
135    // 0021 7F   PUSH32
136    // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
137    // 0042 5B   JUMPDEST
138    // 0043 01   ADD
139    // 0044 60   PUSH1 0x00
140    // 0046 55   SSTORE
141    let code: Vec<u8> = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
142
143    // when
144    let cache_item =
145        SharedCache::<false>::find_jump_and_sub_destinations(&code);
146
147    // then
148    assert!(cache_item
149        .jump_destination
150        .0
151        .iter()
152        .eq(vec![66].into_iter()));
153    assert!(cache_item.sub_entrypoint.0.is_empty());
154}
155
156#[test]
157fn test_find_jump_destinations_not_in_data_segments() {
158    // given
159
160    // 0000 60 06   PUSH1 06
161    // 0002 56      JUMP
162    // 0003 50 5B   PUSH1 0x5B
163    // 0005 56      STOP
164    // 0006 5B      JUMPDEST
165    // 0007 60 04   PUSH1 04
166    // 0009 56      JUMP
167    let code: Vec<u8> = "600656605B565B6004".from_hex().unwrap();
168
169    // when
170    let cache_item =
171        SharedCache::<false>::find_jump_and_sub_destinations(&code);
172
173    // then
174    assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter()));
175    assert!(cache_item.sub_entrypoint.0.is_empty());
176}
177
178#[test]
179fn test_find_sub_entrypoints() {
180    // given
181
182    // see https://eips.ethereum.org/EIPS/eip-2315 for disassembly
183    let code: Vec<u8> =
184        "6800000000000000000c5e005c60115e5d5c5d".from_hex().unwrap();
185
186    // when
187    let cache_item =
188        SharedCache::<false>::find_jump_and_sub_destinations(&code);
189
190    // then
191    assert!(cache_item.jump_destination.0.is_empty());
192    assert!(cache_item
193        .sub_entrypoint
194        .0
195        .iter()
196        .eq(vec![12, 17].into_iter()));
197}
198
199#[test]
200fn test_find_jump_and_sub_allowing_unknown_opcodes() {
201    // precondition
202    assert!(Instruction::from_u8(0xcc) == None);
203
204    // given
205
206    // 0000 5B   JUMPDEST
207    // 0001 CC   ???
208    // 0002 5C   BEGINSUB
209    let code: Vec<u8> = "5BCC5C".from_hex().unwrap();
210
211    // when
212    let cache_item =
213        SharedCache::<false>::find_jump_and_sub_destinations(&code);
214
215    // then
216    assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter()));
217    assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter()));
218}