forest/state_manager/
chain_rand.rs1use std::{io::Write, sync::Arc};
5
6use crate::beacon::{BeaconEntry, BeaconSchedule};
7use crate::blocks::Tipset;
8use crate::chain::index::{ChainIndex, ResolveNullTipset};
9use crate::networks::ChainConfig;
10use crate::shim::clock::ChainEpoch;
11use crate::shim::externs::Rand;
12use crate::utils::encoding::blake2b_256;
13use anyhow::{Context as _, bail};
14use blake2b_simd::Params;
15use byteorder::{BigEndian, WriteBytesExt};
16use fvm_ipld_blockstore::Blockstore;
17
18pub struct ChainRand<DB> {
20 chain_config: Arc<ChainConfig>,
21 tipset: Arc<Tipset>,
22 chain_index: Arc<ChainIndex<Arc<DB>>>,
23 beacon: Arc<BeaconSchedule>,
24}
25
26impl<DB> Clone for ChainRand<DB> {
27 fn clone(&self) -> Self {
28 ChainRand {
29 chain_config: self.chain_config.clone(),
30 tipset: Arc::clone(&self.tipset),
31 chain_index: self.chain_index.clone(),
32 beacon: self.beacon.clone(),
33 }
34 }
35}
36
37impl<DB> ChainRand<DB>
38where
39 DB: Blockstore,
40{
41 pub fn new(
42 chain_config: Arc<ChainConfig>,
43 tipset: Arc<Tipset>,
44 chain_index: Arc<ChainIndex<Arc<DB>>>,
45 beacon: Arc<BeaconSchedule>,
46 ) -> Self {
47 Self {
48 chain_config,
49 tipset,
50 chain_index,
51 beacon,
52 }
53 }
54
55 pub fn get_chain_randomness(
58 &self,
59 round: ChainEpoch,
60 lookback: bool,
61 ) -> anyhow::Result<[u8; 32]> {
62 let ts = Arc::clone(&self.tipset);
63
64 if round > ts.epoch() {
65 bail!("cannot draw randomness from the future");
66 }
67
68 let search_height = if round < 0 { 0 } else { round };
69
70 let resolve = if lookback {
71 ResolveNullTipset::TakeOlder
72 } else {
73 ResolveNullTipset::TakeNewer
74 };
75 let rand_ts = self
76 .chain_index
77 .tipset_by_height(search_height, ts, resolve)?;
78
79 Ok(digest(
80 rand_ts
81 .min_ticket()
82 .context("No ticket exists for block")?
83 .vrfproof
84 .as_bytes(),
85 ))
86 }
87
88 pub fn get_chain_randomness_v2(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> {
90 self.get_chain_randomness(round, false)
91 }
92
93 pub fn get_beacon_randomness_v2(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> {
95 self.get_beacon_randomness(round, false)
96 }
97
98 pub fn get_beacon_randomness_v3(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> {
100 if round < 0 {
101 return self.get_beacon_randomness_v2(round);
102 }
103
104 let beacon_entry = self.extract_beacon_entry_for_epoch(round)?;
105 Ok(digest(beacon_entry.signature()))
106 }
107
108 pub fn get_beacon_randomness(
112 &self,
113 round: ChainEpoch,
114 lookback: bool,
115 ) -> anyhow::Result<[u8; 32]> {
116 let rand_ts: Arc<Tipset> = self.get_beacon_randomness_tipset(round, lookback)?;
117 let be = self.chain_index.latest_beacon_entry(rand_ts)?;
118 Ok(digest(be.signature()))
119 }
120
121 pub fn extract_beacon_entry_for_epoch(&self, epoch: ChainEpoch) -> anyhow::Result<BeaconEntry> {
122 let mut rand_ts: Arc<Tipset> = self.get_beacon_randomness_tipset(epoch, false)?;
123 let (_, beacon) = self.beacon.beacon_for_epoch(epoch)?;
124 let round =
125 beacon.max_beacon_round_for_epoch(self.chain_config.network_version(epoch), epoch);
126
127 for _ in 0..20 {
128 let cbe = &rand_ts.block_headers().first().beacon_entries;
129 for v in cbe {
130 if v.round() == round {
131 return Ok(v.clone());
132 }
133 }
134
135 rand_ts = self.chain_index.load_required_tipset(rand_ts.parents())?;
136 }
137
138 bail!(
139 "didn't find beacon for round {:?} (epoch {:?})",
140 round,
141 epoch
142 )
143 }
144
145 pub fn get_beacon_randomness_tipset(
146 &self,
147 round: ChainEpoch,
148 lookback: bool,
149 ) -> anyhow::Result<Arc<Tipset>> {
150 let ts = Arc::clone(&self.tipset);
151
152 if round > ts.epoch() {
153 bail!("cannot draw randomness from the future");
154 }
155
156 let search_height = if round < 0 { 0 } else { round };
157
158 let resolve = if lookback {
159 ResolveNullTipset::TakeOlder
160 } else {
161 ResolveNullTipset::TakeNewer
162 };
163
164 self.chain_index
165 .tipset_by_height(search_height, ts, resolve)
166 .map_err(|e| e.into())
167 }
168}
169
170impl<DB> Rand for ChainRand<DB>
171where
172 DB: Blockstore,
173{
174 fn get_chain_randomness(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> {
175 self.get_chain_randomness_v2(round)
176 }
177
178 fn get_beacon_randomness(&self, round: ChainEpoch) -> anyhow::Result<[u8; 32]> {
179 self.get_beacon_randomness_v3(round)
180 }
181}
182
183pub fn draw_randomness(
185 rbase: &[u8],
186 pers: i64,
187 round: ChainEpoch,
188 entropy: &[u8],
189) -> anyhow::Result<[u8; 32]> {
190 let mut state = Params::new().hash_length(32).to_state();
191 state.write_i64::<BigEndian>(pers)?;
192 let vrf_digest = digest(rbase);
193 state.write_all(&vrf_digest)?;
194 state.write_i64::<BigEndian>(round)?;
195 state.write_all(entropy)?;
196 let mut ret = [0u8; 32];
197 ret.clone_from_slice(state.finalize().as_bytes());
198 Ok(ret)
199}
200
201pub fn draw_randomness_from_digest(
203 digest: &[u8; 32],
204 pers: i64,
205 round: ChainEpoch,
206 entropy: &[u8],
207) -> anyhow::Result<[u8; 32]> {
208 let mut state = Params::new().hash_length(32).to_state();
209 state.write_i64::<BigEndian>(pers)?;
210 state.write_all(digest)?;
211 state.write_i64::<BigEndian>(round)?;
212 state.write_all(entropy)?;
213 let mut ret = [0u8; 32];
214 ret.clone_from_slice(state.finalize().as_bytes());
215 Ok(ret)
216}
217
218pub fn digest(rbase: &[u8]) -> [u8; 32] {
221 blake2b_256(rbase)
222}