use super::*;
use crate::chain::AtFinalityResolution;
use crate::prelude::*;
use crate::shim::address::Payload;
use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _};
impl StateManager {
pub fn get_bls_public_key(
db: &(impl Blockstore + ShallowClone),
addr: Address,
state_cid: Cid,
) -> Result<BlsPublicKey, Error> {
let state =
StateTree::new_from_root(db, &state_cid).map_err(|e| Error::Other(e.to_string()))?;
let kaddr = state
.resolve_to_deterministic_address(db, addr)
.context("Failed to resolve key address")?;
match kaddr.into_payload() {
Payload::BLS(key) => BlsPublicKey::from_bytes(&key)
.context("Failed to construct bls public key")
.map_err(Error::from),
_ => Err(Error::state(
"Address must be BLS address to load bls public key",
)),
}
}
pub fn lookup_id(&self, addr: &Address, ts: &Tipset) -> Result<Option<Address>, Error> {
let state_tree =
StateTree::new_from_root(self.db(), ts.parent_state()).map_err(|e| format!("{e:?}"))?;
Ok(state_tree
.lookup_id(addr)
.map_err(|e| Error::Other(e.to_string()))?
.map(Address::new_id))
}
pub fn lookup_required_id(&self, addr: &Address, ts: &Tipset) -> Result<Address, Error> {
self.lookup_id(addr, ts)?
.ok_or_else(|| Error::Other(format!("Failed to lookup the id address {addr}")))
}
pub async fn resolve_to_deterministic_address(
&self,
address: Address,
ts: &Tipset,
) -> anyhow::Result<Address> {
use crate::shim::address::Protocol::*;
match address.protocol() {
BLS | Secp256k1 | Delegated => Ok(address),
Actor => anyhow::bail!("cannot resolve actor address to key address"),
ID => {
let id = address.id()?;
let Some(cache) = &self.id_to_deterministic_address_cache else {
return self.resolve_id_address_at_tipset(address, ts).await;
};
if let Some(resolved) = cache.get(&id) {
return Ok(resolved);
}
let at_finality = {
let cs = self.chain_store().shallow_clone();
let ts = ts.clone();
tokio::task::spawn_blocking(move || {
cs.resolve_to_deterministic_address_at_finality(&address, &ts)
})
.await
.context("tokio join error")?
};
if let Ok(resolution) = at_finality {
return Ok(match resolution {
AtFinalityResolution::ReorgStable(resolved) => {
cache.insert(id, resolved);
resolved
}
AtFinalityResolution::Unstable(resolved) => resolved,
});
}
self.resolve_id_address_at_tipset(address, ts).await
}
}
}
async fn resolve_id_address_at_tipset(
&self,
address: Address,
ts: &Tipset,
) -> anyhow::Result<Address> {
if let Ok(state) = self.get_state_tree(ts.parent_state())
&& let Ok(address) = state.resolve_to_deterministic_address(self.db(), address)
{
return Ok(address);
}
let TipsetState { state_root, .. } = self.load_tipset_state(ts).await?;
let state = self.get_state_tree(&state_root)?;
state.resolve_to_deterministic_address(self.db(), address)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blocks::{CachingBlockHeader, RawBlockHeader, Tipset};
use crate::chain::ChainStore;
use crate::db::{DbImpl, MemoryDB};
use crate::networks::ChainConfig;
use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion};
use crate::test_utils::dummy_ticket;
use crate::utils::db::CborStoreExt as _;
const OLD_ACTOR: u64 = 300;
const YOUNG_ACTOR: u64 = 400;
fn setup_with_finality(chain_finality: ChainEpoch) -> (StateManager, Tipset, Address, Address) {
let db: DbImpl = Arc::new(MemoryDB::default()).into();
let mut cfg = ChainConfig::default();
cfg.policy.chain_finality = chain_finality;
let cfg = Arc::new(cfg);
let bls_a = Address::new_bls(&[8u8; 48]).unwrap();
let bls_b = Address::new_bls(&[9u8; 48]).unwrap();
let mut st_a = StateTree::new(&db, StateTreeVersion::V5).unwrap();
st_a.set_actor(
&Address::new_id(OLD_ACTOR),
ActorState::new_empty(Cid::default(), Some(bls_a)),
)
.unwrap();
let root_a = st_a.flush().unwrap();
let mut st_b = StateTree::new_from_root(&db, &root_a).unwrap();
st_b.set_actor(
&Address::new_id(YOUNG_ACTOR),
ActorState::new_empty(Cid::default(), Some(bls_b)),
)
.unwrap();
let root_b = st_b.flush().unwrap();
let genesis = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
ticket: dummy_ticket(0),
state_root: root_a,
timestamp: 1,
..Default::default()
}));
db.put_cbor_default(genesis.block_headers().first())
.unwrap();
let ts1 = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
parents: genesis.key().clone(),
ticket: dummy_ticket(1),
epoch: 1,
state_root: root_a,
timestamp: 1,
..Default::default()
}));
db.put_cbor_default(ts1.block_headers().first()).unwrap();
let head = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
parents: ts1.key().clone(),
ticket: dummy_ticket(2),
epoch: 2,
state_root: root_b,
timestamp: 2,
..Default::default()
}));
db.put_cbor_default(head.block_headers().first()).unwrap();
let cs = ChainStore::new(db, cfg, genesis.block_headers().first().clone()).unwrap();
let sm = StateManager::new(cs).unwrap();
(sm, head, bls_a, bls_b)
}
#[tokio::test]
async fn caches_resolution_witnessed_at_finality_lookback() {
let (sm, head, bls_a, bls_b) = setup_with_finality(1);
let resolved = sm
.resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
.await
.unwrap();
assert_eq!(resolved, bls_a);
let cache = sm.id_to_deterministic_address_cache().unwrap();
assert_eq!(cache.get(&OLD_ACTOR), Some(bls_a));
cache.insert(OLD_ACTOR, bls_b);
let resolved = sm
.resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
.await
.unwrap();
assert_eq!(resolved, bls_b);
}
#[tokio::test]
async fn does_not_cache_actor_younger_than_finality() {
let (sm, head, _bls_a, bls_b) = setup_with_finality(1);
let resolved = sm
.resolve_to_deterministic_address(Address::new_id(YOUNG_ACTOR), &head)
.await
.unwrap();
assert_eq!(resolved, bls_b);
let cache = sm.id_to_deterministic_address_cache().unwrap();
assert_eq!(cache.get(&YOUNG_ACTOR), None);
}
#[tokio::test]
async fn does_not_cache_on_chain_younger_than_finality() {
let (sm, head, _bls_a, bls_b) = setup_with_finality(900);
let resolved = sm
.resolve_to_deterministic_address(Address::new_id(YOUNG_ACTOR), &head)
.await
.unwrap();
assert_eq!(resolved, bls_b);
assert_eq!(
sm.id_to_deterministic_address_cache().unwrap().len(),
0,
"nothing may be cached without a finality-deep witness"
);
}
#[tokio::test]
async fn does_not_cache_at_exact_finality_boundary() {
let (sm, head, bls_a, _bls_b) = setup_with_finality(2);
let resolved = sm
.resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
.await
.unwrap();
assert_eq!(resolved, bls_a);
assert_eq!(
sm.id_to_deterministic_address_cache().unwrap().len(),
0,
"epoch == chain_finality is not finality-deep and must not be cached"
);
}
#[tokio::test]
async fn non_id_addresses_bypass_cache_and_lookback() {
let (sm, head, bls_a, _bls_b) = setup_with_finality(1);
let resolved = sm
.resolve_to_deterministic_address(bls_a, &head)
.await
.unwrap();
assert_eq!(resolved, bls_a);
assert_eq!(sm.id_to_deterministic_address_cache().unwrap().len(), 0);
}
}