Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial block simulation #12

Merged
merged 9 commits into from
Jul 12, 2024
4 changes: 4 additions & 0 deletions src/client/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ impl RpcApi for Client {

self.ledger.add_transaction_unconditionally(tx)?;

// Finally, increase the block height.
let current_height = self.ledger.get_block_height();
self.ledger.set_block_height(current_height + block_num);

Ok(vec![BlockHash::all_zeros(); block_num as usize])
}

Expand Down
110 changes: 110 additions & 0 deletions src/ledger/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! # Block Related Ledger Operations

use super::Ledger;
use bitcoin::Txid;
use rusqlite::params;

impl Ledger {
/// Returns current block height.
///
/// # Panics
///
/// Will panic if cannot get height from database.
pub fn get_block_height(&self) -> u64 {
self.database
.lock()
.unwrap()
.query_row("SELECT height FROM blocks", params![], |row| {
let body = row.get::<_, i64>(0).unwrap();

Ok(body as u64)
})
.unwrap()
}

/// Returns specified transaction's block height.
///
/// # Panics
///
/// Will panic if cannot get height from database.
pub fn get_tx_block_height(&self, txid: Txid) -> u64 {
self.database
.lock()
.unwrap()
.query_row(
"SELECT (block_height) FROM transactions WHERE txid = ?1",
params![txid.to_string()],
|row| {
let body = row.get::<_, i64>(0).unwrap();

Ok(body as u64)
},
)
.unwrap()
}

/// Sets block height to given value.
///
/// # Panics
///
/// Will panic if cannot set height to database.
pub fn set_block_height(&self, height: u64) {
self.database
.lock()
.unwrap()
.execute("UPDATE blocks SET height = ?1", params![height])
.unwrap();
}

/// Increments block height by 1.
///
/// # Panics
///
/// Will panic if either [`get_block_height`] or [`set_block_height`]
/// panics.
pub fn increment_block_height(&self) {
let current_height = self.get_block_height();
self.set_block_height(current_height + 1);
}
}

#[cfg(test)]
mod tests {
use crate::ledger::Ledger;

#[test]
fn get_set_block_height() {
let ledger = Ledger::new("get_set_block_height");

let current_height = ledger.get_block_height();
assert_eq!(current_height, 0);

ledger.set_block_height(0x45);
let current_height = ledger.get_block_height();
assert_eq!(current_height, 0x45);

ledger.set_block_height(0x1F);
let current_height = ledger.get_block_height();
assert_eq!(current_height, 0x1F);
}

#[test]
fn increment_block_height() {
let ledger = Ledger::new("increment_block_height");

let current_height = ledger.get_block_height();
assert_eq!(current_height, 0);

ledger.increment_block_height();
let current_height = ledger.get_block_height();
assert_eq!(current_height, 1);

ledger.set_block_height(0x45);
let current_height = ledger.get_block_height();
assert_eq!(current_height, 0x45);

ledger.increment_block_height();
let current_height = ledger.get_block_height();
assert_eq!(current_height, 0x46);
}
}
2 changes: 2 additions & 0 deletions src/ledger/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub enum LedgerError {
Utxo(String),
#[error("SpendingRequirements error: {0}")]
SpendingRequirements(String),
#[error("Script error: {0}")]
Script(String),
#[error("Anyhow error: {0}")]
AnyHow(anyhow::Error),
}
Expand Down
17 changes: 13 additions & 4 deletions src/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ use std::{
};

mod address;
mod block;
mod errors;
mod macros;
mod script;
mod spending_requirements;
mod transactions;
// mod utxo;
Expand Down Expand Up @@ -53,6 +55,7 @@ impl Ledger {
pub fn drop_databases(database: &Connection) -> Result<(), rusqlite::Error> {
database.execute_batch(
"
DROP TABLE IF EXISTS blocks;
DROP TABLE IF EXISTS transactions;
DROP TABLE IF EXISTS utxos;
",
Expand All @@ -61,12 +64,18 @@ impl Ledger {

pub fn create_databases(database: &Connection) -> Result<(), rusqlite::Error> {
database.execute_batch(
"
CREATE TABLE \"transactions\"
"CREATE TABLE blocks
(
height integer not null
);
INSERT INTO blocks (height) VALUES (0);

CREATE TABLE transactions
(
txid TEXT not null
txid TEXT not null
constraint txid primary key,
body blob not null
block_height integer not null,
body blob not null
);

CREATE TABLE utxos
Expand Down
76 changes: 76 additions & 0 deletions src/ledger/script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! # Script Related Ledger Operations

use super::{errors::LedgerError, Ledger};
use bitcoin::{
opcodes::all::{OP_CSV, OP_PUSHNUM_1},
script, ScriptBuf,
};
use bitcoin_scriptexec::{Exec, ExecCtx, Options, TxTemplate};

impl Ledger {
pub fn run_script(
&self,
ctx: ExecCtx,
tx_template: TxTemplate,
script_buf: ScriptBuf,
script_witness: Vec<Vec<u8>>,
input_block_heights: &Vec<u64>,
) -> Result<(), LedgerError> {
let _prev_outs = tx_template.prevouts.clone();
let input_idx = tx_template.input_idx.clone();

let mut exec = Exec::new(
ctx,
Options::default(),
tx_template,
script_buf.clone(),
script_witness,
)
.map_err(|e| LedgerError::SpendingRequirements(format!("Script format error: {:?}", e)))?;

// Check for CSV.
if {
let mut ret = true;
let mut instructions = script_buf.instructions();
let op1 = instructions.next();
let op2 = instructions.next();

if let (Some(Ok(op1)), Some(Ok(op2))) = (op1, op2) {
if op2 == script::Instruction::Op(OP_CSV) {
let height = op1.opcode().unwrap().to_u8();
let height = height - (OP_PUSHNUM_1.to_u8() - 1);

let current_height = self.get_block_height();

if current_height - input_block_heights[input_idx] < height as u64 {
ret = false;
}
}
}

ret
} == false
{
return Err(LedgerError::Script(format!(
"TX is locked for the block height: "
)));
}

loop {
let res = exec.exec_next();
if res.is_err() {
break;
}
}

let res = exec.result().unwrap();
if !res.success {
return Err(LedgerError::Script(format!(
"The script execution is not successful: {:?}",
res
)));
}

Ok(())
}
}
Loading