use std::sync::Arc;
use arc_swap::ArcSwap;
use peer_http::HttpApi;
use peer_manager::PeerManager;
use rpc::Rpc;
use rpc_client::RpcClient;
use tape_api::program::tapedrive;
use tape_core::prelude::{CompressedTrack, StorageUnits};
use tape_core::types::coin::{SOL, TAPE};
use tape_core::types::ContentType;
use tape_crypto::prelude::{Address, Keypair};
use tape_protocol::{Api, ProtocolState};
use tokio::io::{AsyncRead, AsyncWrite};
use crate::balance::{sol_balance_of, tape_balance_of};
use crate::bootstrap::{BootstrapStore, Reputation};
use crate::error::TapedriveError;
use crate::keys::operator::TapeOperator;
use crate::keys::tape_key::TapeKey;
use crate::metrics::{Metrics, Noop, Operation, Phase, Timer};
use crate::read_options::ReadOptions;
use crate::write_options::WriteOptions;
use crate::stream::{
read::{read_bytes, read_into},
receipt::StreamReceipt,
write::{write_bytes as write_stream_bytes, write_stream as write_reader_stream},
};
use crate::track::write::{write_or_resume, UNNAMED_TRACK, UNTYPED_TRACK};
pub struct Tapedrive<Blockchain: Rpc, Cluster: Api> {
pub state: ArcSwap<ProtocolState>,
pub peer_manager: Arc<PeerManager>,
pub api: Arc<Cluster>,
pub rpc: Arc<RpcClient<Blockchain>>,
pub payer: Option<Keypair>,
pub metrics: Arc<dyn Metrics>,
pub write_options: WriteOptions,
pub read_options: ReadOptions,
pub reputation: Arc<Reputation>,
}
impl<Blockchain: Rpc> Tapedrive<Blockchain, HttpApi> {
pub fn new(rpc: Blockchain, payer: Keypair) -> Self {
Self::new_read_only(rpc).with_payer(payer)
}
pub fn new_read_only(rpc: Blockchain) -> Self {
let rpc_client = Arc::new(RpcClient::from_rpc(rpc));
let peer_manager = Arc::new(PeerManager::new());
let api = Arc::new(HttpApi::with_default_timeouts(peer_manager.clone()));
Self::from_parts(
ArcSwap::from_pointee(ProtocolState::default()),
peer_manager,
api,
rpc_client,
None,
)
}
}
impl<Blockchain: Rpc, Cluster: Api> Tapedrive<Blockchain, Cluster> {
pub fn from_parts(
state: ArcSwap<ProtocolState>,
peer_manager: Arc<PeerManager>,
api: Arc<Cluster>,
rpc: Arc<RpcClient<Blockchain>>,
payer: Option<Keypair>,
) -> Self {
Self {
state,
peer_manager,
api,
rpc,
payer,
metrics: Arc::new(Noop),
write_options: WriteOptions::default(),
read_options: ReadOptions::default(),
reputation: Arc::new(Reputation::detached()),
}
}
pub fn store_state(&self, state: ProtocolState) {
state.touch();
self.state.store(Arc::new(state));
}
pub fn with_reputation(mut self, reputation: Arc<Reputation>) -> Self {
self.reputation = reputation;
self
}
pub fn with_bootstrap_cache(mut self, store: BootstrapStore) -> Self {
self.reputation = Arc::new(Reputation::attach(store, tapedrive::id().into()));
self
}
pub fn with_payer(mut self, payer: Keypair) -> Self {
self.payer = Some(payer);
self
}
pub fn with_write_options(mut self, options: WriteOptions) -> Self {
self.write_options = options;
self
}
pub fn with_read_options(mut self, options: ReadOptions) -> Self {
self.read_options = options;
self
}
pub fn with_metrics(mut self, metrics: Arc<dyn Metrics>) -> Self {
self.metrics = metrics;
self
}
pub fn rpc(&self) -> &RpcClient<Blockchain> {
&self.rpc
}
pub fn state(&self) -> arc_swap::Guard<Arc<ProtocolState>> {
self.state.load()
}
pub fn payer(&self) -> Result<&Keypair, TapedriveError> {
self.payer.as_ref().ok_or(TapedriveError::MissingPayer)
}
pub async fn sol_balance(&self) -> Result<SOL, TapedriveError> {
sol_balance_of(&self.rpc, &self.payer()?.address()).await
}
pub async fn tape_balance(&self) -> Result<TAPE, TapedriveError> {
tape_balance_of(&self.rpc, &self.payer()?.address()).await
}
pub(crate) fn timer(&self, operation: Operation, phase: Phase) -> Timer<'_> {
Timer::start(self.metrics.as_ref(), operation, phase)
}
pub async fn write(
&self,
tape_key: &TapeKey,
data: &[u8],
epochs: u64,
) -> Result<CompressedTrack, TapedriveError> {
self.write_named(
tape_key,
UNNAMED_TRACK,
UNTYPED_TRACK,
data,
epochs,
)
.await
}
pub async fn write_named(
&self,
tape_key: &TapeKey,
name: impl AsRef<[u8]>,
content_type: ContentType,
data: &[u8],
epochs: u64,
) -> Result<CompressedTrack, TapedriveError> {
let total = self
.timer(Operation::Write, Phase::Total)
.bytes(data.len() as u64);
let result =
write_or_resume(self, tape_key, name.as_ref(), content_type, data, epochs).await;
total.finish_result(&result);
result
}
pub async fn write_bytes(
&self,
tape_key: &TapeKey,
data: &[u8],
) -> Result<StreamReceipt, TapedriveError> {
self.write_named_bytes(
tape_key,
UNNAMED_TRACK,
UNTYPED_TRACK,
data,
)
.await
}
pub async fn write_named_bytes(
&self,
tape_key: &TapeKey,
name: impl AsRef<[u8]>,
content_type: ContentType,
data: &[u8],
) -> Result<StreamReceipt, TapedriveError> {
self.write_named_bytes_as(tape_key, name, content_type, data)
.await
}
pub async fn write_named_bytes_as(
&self,
operator: &impl TapeOperator,
name: impl AsRef<[u8]>,
content_type: ContentType,
data: &[u8],
) -> Result<StreamReceipt, TapedriveError> {
let timer = self
.timer(Operation::WriteStream, Phase::Total)
.bytes(data.len() as u64);
let result = write_stream_bytes(self, operator, name.as_ref(), content_type, data).await;
timer.finish_result(&result);
result
}
pub async fn write_stream<Reader: AsyncRead + Unpin>(
&self,
tape_key: &TapeKey,
size: StorageUnits,
reader: Reader,
) -> Result<StreamReceipt, TapedriveError> {
self.write_named_stream(
tape_key,
UNNAMED_TRACK,
UNTYPED_TRACK,
size,
reader,
)
.await
}
pub async fn write_named_stream<Reader: AsyncRead + Unpin>(
&self,
tape_key: &TapeKey,
name: impl AsRef<[u8]>,
content_type: ContentType,
size: StorageUnits,
reader: Reader,
) -> Result<StreamReceipt, TapedriveError> {
self.write_named_stream_as(tape_key, name, content_type, size, reader)
.await
}
pub async fn write_named_stream_as<Reader: AsyncRead + Unpin>(
&self,
operator: &impl TapeOperator,
name: impl AsRef<[u8]>,
content_type: ContentType,
size: StorageUnits,
reader: Reader,
) -> Result<StreamReceipt, TapedriveError> {
let timer = self
.timer(Operation::WriteStream, Phase::Total)
.bytes(size.to_bytes());
let result =
write_reader_stream(self, operator, name.as_ref(), content_type, size, reader).await;
timer.finish_result(&result);
result
}
pub async fn read_bytes(
&self,
manifest: &Address,
) -> Result<Vec<u8>, TapedriveError> {
let timer = self.timer(Operation::ReadStream, Phase::Total);
let result = read_bytes(self, manifest).await;
let timer = match &result {
Ok(bytes) => timer.bytes(bytes.len() as u64),
Err(_) => timer,
};
timer.finish_result(&result);
result
}
pub async fn read_into<Writer: AsyncWrite + Unpin>(
&self,
manifest: &Address,
writer: Writer,
) -> Result<(), TapedriveError> {
let timer = self.timer(Operation::ReadStream, Phase::Total);
let result = read_into(self, manifest, writer).await;
timer.finish_result(&result);
result
}
}
#[cfg(test)]
mod tests {
use rpc_litesvm::LiteSvmRpc;
use tape_crypto::prelude::Keypair;
use super::*;
#[tokio::test]
async fn balances() {
let rpc = LiteSvmRpc::new();
let payer = Keypair::new(&mut rand::thread_rng());
let address = payer.address();
let client = Tapedrive::new(rpc.clone(), payer);
assert_eq!(client.sol_balance().await.expect("sol balance"), SOL(0));
assert_eq!(client.tape_balance().await.expect("tape balance"), TAPE(0));
rpc.airdrop(&address.into(), 5_000_000_000).expect("airdrop");
assert_eq!(
client.sol_balance().await.expect("sol balance"),
SOL(5_000_000_000)
);
}
}