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

Update getutxo #237

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/floresta-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ fn do_request(cmd: &Cli, client: ReqwestClient) -> anyhow::Result<String> {
Methods::GetPeerInfo => serde_json::to_string_pretty(&client.get_peer_info()?)?,
Methods::Stop => serde_json::to_string_pretty(&client.stop()?)?,
Methods::AddNode { node } => serde_json::to_string_pretty(&client.add_node(node)?)?,
Methods::FindTxOut {
txid,
vout,
script,
height_hint,
} => serde_json::to_string_pretty(&client.find_tx_out(
txid,
vout,
script,
height_hint.unwrap_or(0),
)?)?,
})
}

Expand Down Expand Up @@ -147,4 +158,11 @@ pub enum Methods {
/// Usage: addnode <ip:[port]>
#[command(name = "addnode")]
AddNode { node: String },
#[command(name = "findtxout")]
FindTxOut {
txid: Txid,
vout: u32,
script: String,
height_hint: Option<u32>,
},
}
37 changes: 33 additions & 4 deletions crates/floresta-cli/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ pub trait FlorestaRPC {
/// is returned as a hexadecimal string. If the verbosity flag is 1, the block is returned
/// as a json object.
fn get_block(&self, hash: BlockHash) -> Result<GetBlockRes>;
/// Finds an specific utxo in the chain
/// Return a cached transaction output
///
/// You can use this to look for a utxo. If it exists, it will return the amount and
/// scriptPubKey of this utxo. It returns an empty object if the utxo doesn't exist.
/// You must have enabled block filters by setting the `blockfilters=1` option.
/// This method returns a cached transaction output. If the output is not in the cache,
/// or is spent, an empty object is returned. If you want to find a utxo that's not in
/// the cache, you can use the findtxout method.
fn get_tx_out(&self, tx_id: Txid, outpoint: u32) -> Result<Value>;
/// Stops the florestad process
///
Expand All @@ -105,6 +105,18 @@ pub trait FlorestaRPC {
///
/// You can use this to connect with a given node, providing it's IP address and port.
fn add_node(&self, node: String) -> Result<bool>;
/// Finds an specific utxo in the chain
///
/// You can use this to look for a utxo. If it exists, it will return the amount and
/// scriptPubKey of this utxo. It returns an empty object if the utxo doesn't exist.
/// You must have enabled block filters by setting the `blockfilters=1` option.
fn find_tx_out(
&self,
tx_id: Txid,
outpoint: u32,
script: String,
height_hint: u32,
) -> Result<Value>;
}

/// Since the workflow for jsonrpc is the same for all methods, we can implement a trait
Expand All @@ -120,6 +132,23 @@ pub trait JsonRPCClient: Sized {
}

impl<T: JsonRPCClient> FlorestaRPC for T {
fn find_tx_out(
&self,
tx_id: Txid,
outpoint: u32,
script: String,
height_hint: u32,
) -> Result<Value> {
self.call(
"findtxout",
&[
Value::String(tx_id.to_string()),
Value::Number(Number::from(outpoint)),
Value::String(script),
Value::Number(Number::from(height_hint)),
],
)
}
fn add_node(&self, node: String) -> Result<bool> {
self.call("addnode", &[Value::String(node)])
}
Expand Down
66 changes: 60 additions & 6 deletions crates/floresta-compact-filters/src/flat_filters_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ impl Iterator for FiltersIterator {

struct FlatFiltersStoreInner {
file: std::fs::File,
index: std::fs::File,
path: PathBuf,
}

Expand All @@ -67,7 +68,19 @@ impl FlatFiltersStore {
.open(&path)
.unwrap();

Self(Mutex::new(FlatFiltersStoreInner { file, path }))
let index = format!("{}-index", path.to_string_lossy());
let mut index = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(index)
.unwrap();

index.seek(SeekFrom::Start(0)).unwrap();
index.write_all(&4_u64.to_le_bytes()).unwrap();

Self(Mutex::new(FlatFiltersStoreInner { file, path, index }))
}
}

Expand All @@ -82,8 +95,20 @@ impl TryFrom<&PathBuf> for FlatFiltersStore {
.truncate(false)
.open(path)?;

let index = format!("{}-index", path.to_string_lossy());
let mut index = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(index)?;

index.seek(SeekFrom::Start(0))?;
index.write_all(&4_u64.to_le_bytes())?;

Ok(Self(Mutex::new(FlatFiltersStoreInner {
file,
index,
path: path.clone(),
})))
}
Expand Down Expand Up @@ -121,11 +146,29 @@ impl IteratableFilterStore for FlatFiltersStore {
Ok(u32::from_le_bytes(buf))
}

fn iter(&self) -> Result<Self::I, IteratableFilterStoreError> {
let inner = self.0.lock()?;
fn iter(&self, start_height: Option<usize>) -> Result<Self::I, IteratableFilterStoreError> {
let mut inner = self.0.lock()?;
let new_file = File::open(inner.path.clone())?;
let mut reader = BufReader::new(new_file);
reader.seek(SeekFrom::Start(4))?;

let start_height = start_height.unwrap_or(0) as u32;

// round down to the nearest 50_000
let start_height = start_height - (start_height % 50_000);

// take the index by dividing by 50_000
let index = (start_height / 50_000) * 8;

// seek to the index
inner.index.seek(SeekFrom::Start(index as u64))?;

// read the position of the file
let mut buf = [0; 8];
inner.index.read_exact(&mut buf)?;
let pos = u64::from_le_bytes(buf);

// seek to the position
reader.seek(SeekFrom::Start(pos))?;
Ok(FiltersIterator { reader })
}

Expand All @@ -142,7 +185,17 @@ impl IteratableFilterStore for FlatFiltersStore {

let mut inner = self.0.lock()?;

inner.file.seek(SeekFrom::End(0))?;
let offset = inner.file.seek(SeekFrom::End(0))?;
// save the position of the file for every 50_000 blocks, so we can
// start the rescan from a given height
if height % 50_000 == 0 {
let index_offset = height / 50_000;
inner
.index
.seek(SeekFrom::Start((index_offset * 8) as u64))?;
inner.index.write_all(&offset.to_le_bytes())?;
}

inner.file.write_all(&height.to_le_bytes())?;
inner.file.write_all(&length.to_le_bytes())?;
inner.file.write_all(&block_filter.content)?;
Expand Down Expand Up @@ -174,10 +227,11 @@ mod tests {
.put_filter(filter.clone(), 1)
.expect("could not put filter");

let mut iter = store.iter().expect("could not get iterator");
let mut iter = store.iter(Some(0)).expect("could not get iterator");
assert_eq!((1, filter), iter.next().unwrap());

assert_eq!(iter.next(), None);
remove_file(path).expect("could not remove file after test");
remove_file(format!("{}-index", path)).expect("could not remove index after test");
}
}
2 changes: 1 addition & 1 deletion crates/floresta-compact-filters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub trait IteratableFilterStore:
type I: Iterator<Item = (u32, bip158::BlockFilter)>;
/// Fetches the first filter and sets our internal cursor to the first filter,
/// succeeding calls to [next] will return the next filter until we reach the end
fn iter(&self) -> Result<Self::I, IteratableFilterStoreError>;
fn iter(&self, start_height: Option<usize>) -> Result<Self::I, IteratableFilterStoreError>;
/// Writes a new filter to the store
fn put_filter(
&self,
Expand Down
7 changes: 2 additions & 5 deletions crates/floresta-compact-filters/src/network_filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@ impl<Storage: IteratableFilterStore> NetworkFilters<Storage> {
pub fn match_any(
&self,
query: Vec<&[u8]>,
end_height: u32,
start_height: Option<usize>,
chain: impl BlockchainInterface,
) -> Result<Vec<BlockHash>, IteratableFilterStoreError> {
let mut blocks = Vec::new();
let iter = query.into_iter();
for (height, filter) in self.filters.iter()? {
if height >= end_height {
break;
}
for (height, filter) in self.filters.iter(start_height)? {
let hash = chain.get_block_hash(height).unwrap();
if filter.match_any(&hash, &mut iter.clone()).unwrap() {
blocks.push(hash);
Expand Down
3 changes: 1 addition & 2 deletions crates/floresta-electrum/src/electrum_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,13 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {
addresses: Vec<ScriptBuf>,
) -> Result<(), super::error::Error> {
// By default, we look from 1..tip
let height = cfilters.get_height().unwrap();
let mut _addresses = addresses
.iter()
.map(|address| address.as_bytes())
.collect::<Vec<_>>();

// TODO (Davidson): Let users select what the starting and end height is
let blocks = cfilters.match_any(_addresses, height, self.chain.clone());
let blocks = cfilters.match_any(_addresses, Some(0), self.chain.clone());
if blocks.is_err() {
error!("error while rescanning with block filters: {:?}", blocks);
self.addresses_to_scan.extend(addresses); // push them back to get a retry
Expand Down
9 changes: 9 additions & 0 deletions crates/floresta-watch-only/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,15 @@ impl<D: AddressCacheDatabase> AddressCache<D> {
}
}

pub fn get_utxo(&self, outpoint: &OutPoint) -> Option<TxOut> {
let inner = self.inner.read().expect("poisoned lock");
// a dirty way to check if the utxo is still unspent
let _ = inner.utxo_index.get(outpoint)?;
let tx = inner.get_transaction(&outpoint.txid)?;

Some(tx.tx.output[outpoint.vout as usize].clone())
}

pub fn n_cached_addresses(&self) -> usize {
let inner = self.inner.read().expect("poisoned lock");
inner.address_map.len()
Expand Down
11 changes: 11 additions & 0 deletions crates/floresta-wire/src/p2p_wire/seeds/mainnet_seeds.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,16 @@
},
"services": 50331657,
"port": 8333
},
{
"address": {
"V4": "[2804:4308:8c:6700:ee8e:b5ff:fe78:cbcf]"
},
"last_connected": 1678986166,
"state": {
"Tried": 0
},
"services": 73,
"port": 8333
}
]
6 changes: 6 additions & 0 deletions florestad/src/json_rpc/res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub struct BlockJson {
#[derive(Debug)]
pub enum Error {
TxNotFound,
InvalidScript,
InvalidDescriptor,
BlockNotFound,
Chain,
Expand All @@ -108,6 +109,7 @@ pub enum Error {
NoBlockFilters,
InvalidNetwork,
InInitialBlockDownload,
Encode,
}

impl Display for Error {
Expand All @@ -123,6 +125,8 @@ impl Display for Error {
Error::NoBlockFilters => "You don't have block filters enabled, please start florestad with --cfilters to run this RPC",
Error::InvalidNetwork => "Invalid network",
Error::InInitialBlockDownload => "Node is in initial block download, wait until it's finished",
Error::Encode => "Error encoding response",
Error::InvalidScript => "Invalid script",
};
write!(f, "{}", msg)
}
Expand All @@ -141,6 +145,8 @@ impl From<Error> for i64 {
Error::NoBlockFilters => 8,
Error::InvalidNetwork => 9,
Error::InInitialBlockDownload => 10,
Error::Encode => 11,
Error::InvalidScript => 12,
}
}
}
Expand Down
Loading
Loading