use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::secp256k1::SecretKey;
use bitcoin::{Block, BlockHash};
use crate::blockfile::{append_block, read_block, read_index, write_index, Index};
use crate::document::ChainDocument;
use crate::error::{Error, Result};
use crate::state::{Applied, ClaimRequest, NextBlock, State, Submitted};
#[derive(Debug)]
pub struct Chain {
state: State,
dir: PathBuf,
index: Index,
}
pub fn now() -> u32 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as u32)
.unwrap_or(0)
}
impl Chain {
pub fn open(
doc: ChainDocument,
dir: impl AsRef<Path>,
key: Option<&SecretKey>,
) -> Result<Self> {
let dir = dir.as_ref().to_path_buf();
std::fs::create_dir_all(&dir)?;
let dat = dir.join("blocks.dat");
let idx = dir.join("blocks.json");
let (state, index) = match read_index(&idx)? {
None => {
let key = key.ok_or_else(|| {
Error::Chain("no chain on disk and no key to make the genesis".into())
})?;
let genesis = State::genesis_block_for(&doc, key)?;
let state = State::from_genesis(doc, &genesis, None)?;
let mut index = Index::new(&state.document().id);
append_block(
&dat,
&mut index,
0,
&state.genesis_hash().to_string(),
&serialize(&genesis),
)?;
write_index(&idx, &index)?;
(state, index)
}
Some(index) => {
let first = index
.blocks
.first()
.ok_or_else(|| Error::Chain("the block file has no genesis".into()))?;
if first.height != 0 {
return Err(Error::Chain(format!(
"the block file starts at {}, not the genesis",
first.height
)));
}
let genesis: Block = deserialize(&read_block(&dat, first)?)
.map_err(|e| Error::Encoding(e.to_string()))?;
let mut state = State::from_genesis(
doc,
&genesis,
Some(
first
.hash
.parse()
.map_err(|_| Error::Encoding("bad hash in the index".into()))?,
),
)?;
for e in &index.blocks[1..] {
let expect: BlockHash = e
.hash
.parse()
.map_err(|_| Error::Encoding("bad hash in the index".into()))?;
let block: Block = deserialize(&read_block(&dat, e)?)
.map_err(|err| Error::Encoding(err.to_string()))?;
state.apply(e.height, &block, Some(expect), Some(now()))?;
}
(state, index)
}
};
Ok(Self { state, dir, index })
}
pub fn state(&self) -> &State {
&self.state
}
pub fn index(&self) -> &Index {
&self.index
}
pub fn dat_path(&self) -> PathBuf {
self.dir.join("blocks.dat")
}
pub fn index_path(&self) -> PathBuf {
self.dir.join("blocks.json")
}
fn write(&mut self, height: u32, hash: BlockHash, bytes: &[u8]) -> Result<()> {
append_block(
self.dat_path(),
&mut self.index,
height,
&hash.to_string(),
bytes,
)?;
write_index(self.index_path(), &self.index)
}
pub fn add_block(&mut self, bytes: &[u8], expect: Option<BlockHash>) -> Result<Applied> {
let r = self.state.add_block_bytes(bytes, expect, Some(now()))?;
self.write(r.height, r.hash, bytes)?;
Ok(r)
}
pub fn submit(&mut self, tx_bytes: &[u8]) -> Result<Submitted> {
let tx = deserialize(tx_bytes).map_err(|e| Error::Encoding(e.to_string()))?;
self.state.submit(tx)
}
pub fn produce(&mut self, key: &SecretKey, claims: Vec<ClaimRequest>) -> Result<Applied> {
let t = now();
let (r, block) = self
.state
.produce(key, &NextBlock { time: t, claims }, Some(t))?;
self.write(r.height, r.hash, &serialize(&block))?;
Ok(r)
}
}