mirror of
https://github.com/aljazceru/cdk.git
synced 2026-01-31 02:35:55 +01:00
feat(cdk): add generic key-value store functionality for mint databases (#1022)
* feat(cdk): add generic key-value store functionality for mint databases Implements a comprehensive KV store system with transaction support, namespace-based organization, and validation for mint databases. - Add KVStoreDatabase and KVStoreTransaction traits for generic storage - Include namespace and key validation with ASCII character restrictions - Add database migrations for kv_store table in SQLite and PostgreSQL - Implement comprehensive test suite for KV store functionality - Integrate KV store traits into existing Database and Transaction bounds
This commit is contained in:
@@ -2,6 +2,66 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Valid ASCII characters for namespace and key strings in KV store
|
||||
pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
|
||||
|
||||
/// Maximum length for namespace and key strings in KV store
|
||||
pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
|
||||
|
||||
/// Validates that a string contains only valid KV store characters and is within length limits
|
||||
pub fn validate_kvstore_string(s: &str) -> Result<(), Error> {
|
||||
if s.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN {
|
||||
return Err(Error::KVStoreInvalidKey(format!(
|
||||
"{} exceeds maximum length of key characters",
|
||||
KVSTORE_NAMESPACE_KEY_MAX_LEN
|
||||
)));
|
||||
}
|
||||
|
||||
if !s
|
||||
.chars()
|
||||
.all(|c| KVSTORE_NAMESPACE_KEY_ALPHABET.contains(c))
|
||||
{
|
||||
return Err(Error::KVStoreInvalidKey("key contains invalid characters. Only ASCII letters, numbers, underscore, and hyphen are allowed".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates namespace and key parameters for KV store operations
|
||||
pub fn validate_kvstore_params(
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
key: &str,
|
||||
) -> Result<(), Error> {
|
||||
// Validate primary namespace
|
||||
validate_kvstore_string(primary_namespace)?;
|
||||
|
||||
// Validate secondary namespace
|
||||
validate_kvstore_string(secondary_namespace)?;
|
||||
|
||||
// Validate key
|
||||
validate_kvstore_string(key)?;
|
||||
|
||||
// Check empty namespace rules
|
||||
if primary_namespace.is_empty() && !secondary_namespace.is_empty() {
|
||||
return Err(Error::KVStoreInvalidKey(
|
||||
"If primary_namespace is empty, secondary_namespace must also be empty".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check for potential collisions between keys and namespaces in the same namespace
|
||||
let namespace_key = format!("{}/{}", primary_namespace, secondary_namespace);
|
||||
if key == primary_namespace || key == secondary_namespace || key == namespace_key {
|
||||
return Err(Error::KVStoreInvalidKey(format!(
|
||||
"Key '{}' conflicts with namespace names",
|
||||
key
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cashu::quote_id::QuoteId;
|
||||
use cashu::{Amount, MintInfo};
|
||||
@@ -20,6 +80,9 @@ mod auth;
|
||||
#[cfg(feature = "test")]
|
||||
pub mod test;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_kvstore;
|
||||
|
||||
#[cfg(feature = "auth")]
|
||||
pub use auth::{MintAuthDatabase, MintAuthTransaction};
|
||||
|
||||
@@ -263,6 +326,42 @@ pub trait DbTransactionFinalizer {
|
||||
async fn rollback(self: Box<Self>) -> Result<(), Self::Err>;
|
||||
}
|
||||
|
||||
/// Key-Value Store Transaction trait
|
||||
#[async_trait]
|
||||
pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
|
||||
/// Read value from key-value store
|
||||
async fn kv_read(
|
||||
&mut self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
key: &str,
|
||||
) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Write value to key-value store
|
||||
async fn kv_write(
|
||||
&mut self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
key: &str,
|
||||
value: &[u8],
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Remove value from key-value store
|
||||
async fn kv_remove(
|
||||
&mut self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
key: &str,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// List keys in a namespace
|
||||
async fn kv_list(
|
||||
&mut self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
) -> Result<Vec<String>, Error>;
|
||||
}
|
||||
|
||||
/// Base database writer
|
||||
#[async_trait]
|
||||
pub trait Transaction<'a, Error>:
|
||||
@@ -270,6 +369,7 @@ pub trait Transaction<'a, Error>:
|
||||
+ QuotesTransaction<'a, Err = Error>
|
||||
+ SignaturesTransaction<'a, Err = Error>
|
||||
+ ProofsTransaction<'a, Err = Error>
|
||||
+ KVStoreTransaction<'a, Error>
|
||||
{
|
||||
/// Set [`QuoteTTL`]
|
||||
async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error>;
|
||||
@@ -278,10 +378,44 @@ pub trait Transaction<'a, Error>:
|
||||
async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Key-Value Store Database trait
|
||||
#[async_trait]
|
||||
pub trait KVStoreDatabase {
|
||||
/// KV Store Database Error
|
||||
type Err: Into<Error> + From<Error>;
|
||||
|
||||
/// Read value from key-value store
|
||||
async fn kv_read(
|
||||
&self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
key: &str,
|
||||
) -> Result<Option<Vec<u8>>, Self::Err>;
|
||||
|
||||
/// List keys in a namespace
|
||||
async fn kv_list(
|
||||
&self,
|
||||
primary_namespace: &str,
|
||||
secondary_namespace: &str,
|
||||
) -> Result<Vec<String>, Self::Err>;
|
||||
}
|
||||
|
||||
/// Key-Value Store Database trait
|
||||
#[async_trait]
|
||||
pub trait KVStore: KVStoreDatabase {
|
||||
/// Beings a KV transaction
|
||||
async fn begin_transaction<'a>(
|
||||
&'a self,
|
||||
) -> Result<Box<dyn KVStoreTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
|
||||
}
|
||||
|
||||
/// Mint Database trait
|
||||
#[async_trait]
|
||||
pub trait Database<Error>:
|
||||
QuotesDatabase<Err = Error> + ProofsDatabase<Err = Error> + SignaturesDatabase<Err = Error>
|
||||
KVStoreDatabase<Err = Error>
|
||||
+ QuotesDatabase<Err = Error>
|
||||
+ ProofsDatabase<Err = Error>
|
||||
+ SignaturesDatabase<Err = Error>
|
||||
{
|
||||
/// Beings a transaction
|
||||
async fn begin_transaction<'a>(
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
//! implementation
|
||||
use std::str::FromStr;
|
||||
|
||||
// For derivation path parsing
|
||||
use bitcoin::bip32::DerivationPath;
|
||||
use cashu::secret::Secret;
|
||||
use cashu::{Amount, CurrencyUnit, SecretKey};
|
||||
|
||||
use super::*;
|
||||
use crate::database;
|
||||
use crate::database::MintKVStoreDatabase;
|
||||
use crate::mint::MintKeySetInfo;
|
||||
|
||||
#[inline]
|
||||
async fn setup_keyset<DB>(db: &DB) -> Id
|
||||
where
|
||||
DB: KeysDatabase<Err = database::Error>,
|
||||
DB: KeysDatabase<Err = crate::database::Error>,
|
||||
{
|
||||
let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
|
||||
let keyset_info = MintKeySetInfo {
|
||||
@@ -23,7 +25,7 @@ where
|
||||
active: true,
|
||||
valid_from: 0,
|
||||
final_expiry: None,
|
||||
derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
|
||||
derivation_path: DerivationPath::from_str("m/0'/0'/0'").unwrap(),
|
||||
derivation_path_index: Some(0),
|
||||
max_order: 32,
|
||||
input_fee_ppk: 0,
|
||||
@@ -37,7 +39,7 @@ where
|
||||
/// State transition test
|
||||
pub async fn state_transition<DB>(db: DB)
|
||||
where
|
||||
DB: Database<database::Error> + KeysDatabase<Err = database::Error>,
|
||||
DB: Database<crate::database::Error> + KeysDatabase<Err = crate::database::Error>,
|
||||
{
|
||||
let keyset_id = setup_keyset(&db).await;
|
||||
|
||||
@@ -83,7 +85,7 @@ where
|
||||
/// other tests
|
||||
pub async fn add_and_find_proofs<DB>(db: DB)
|
||||
where
|
||||
DB: Database<database::Error> + KeysDatabase<Err = database::Error>,
|
||||
DB: Database<crate::database::Error> + KeysDatabase<Err = crate::database::Error>,
|
||||
{
|
||||
let keyset_id = setup_keyset(&db).await;
|
||||
|
||||
@@ -124,12 +126,99 @@ where
|
||||
assert_eq!(proofs_from_db.unwrap().len(), 2);
|
||||
}
|
||||
|
||||
/// Test KV store functionality including write, read, list, update, and remove operations
|
||||
pub async fn kvstore_functionality<DB>(db: DB)
|
||||
where
|
||||
DB: Database<crate::database::Error> + MintKVStoreDatabase<Err = crate::database::Error>,
|
||||
{
|
||||
// Test basic read/write operations in transaction
|
||||
{
|
||||
let mut tx = Database::begin_transaction(&db).await.unwrap();
|
||||
|
||||
// Write some test data
|
||||
tx.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
|
||||
.await
|
||||
.unwrap();
|
||||
tx.kv_write("test_namespace", "sub_namespace", "key2", b"value2")
|
||||
.await
|
||||
.unwrap();
|
||||
tx.kv_write("test_namespace", "other_sub", "key3", b"value3")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Read back the data in the transaction
|
||||
let value1 = tx
|
||||
.kv_read("test_namespace", "sub_namespace", "key1")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value1, Some(b"value1".to_vec()));
|
||||
|
||||
// List keys in namespace
|
||||
let keys = tx.kv_list("test_namespace", "sub_namespace").await.unwrap();
|
||||
assert_eq!(keys, vec!["key1", "key2"]);
|
||||
|
||||
// Commit transaction
|
||||
tx.commit().await.unwrap();
|
||||
}
|
||||
|
||||
// Test read operations after commit
|
||||
{
|
||||
let value1 = db
|
||||
.kv_read("test_namespace", "sub_namespace", "key1")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value1, Some(b"value1".to_vec()));
|
||||
|
||||
let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
|
||||
assert_eq!(keys, vec!["key1", "key2"]);
|
||||
|
||||
let other_keys = db.kv_list("test_namespace", "other_sub").await.unwrap();
|
||||
assert_eq!(other_keys, vec!["key3"]);
|
||||
}
|
||||
|
||||
// Test update and remove operations
|
||||
{
|
||||
let mut tx = Database::begin_transaction(&db).await.unwrap();
|
||||
|
||||
// Update existing key
|
||||
tx.kv_write("test_namespace", "sub_namespace", "key1", b"updated_value1")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Remove a key
|
||||
tx.kv_remove("test_namespace", "sub_namespace", "key2")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tx.commit().await.unwrap();
|
||||
}
|
||||
|
||||
// Verify updates
|
||||
{
|
||||
let value1 = db
|
||||
.kv_read("test_namespace", "sub_namespace", "key1")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value1, Some(b"updated_value1".to_vec()));
|
||||
|
||||
let value2 = db
|
||||
.kv_read("test_namespace", "sub_namespace", "key2")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value2, None);
|
||||
|
||||
let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
|
||||
assert_eq!(keys, vec!["key1"]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unit test that is expected to be passed for a correct database implementation
|
||||
#[macro_export]
|
||||
macro_rules! mint_db_test {
|
||||
($make_db_fn:ident) => {
|
||||
mint_db_test!(state_transition, $make_db_fn);
|
||||
mint_db_test!(add_and_find_proofs, $make_db_fn);
|
||||
mint_db_test!(kvstore_functionality, $make_db_fn);
|
||||
};
|
||||
($name:ident, $make_db_fn:ident) => {
|
||||
#[tokio::test]
|
||||
|
||||
207
crates/cdk-common/src/database/mint/test_kvstore.rs
Normal file
207
crates/cdk-common/src/database/mint/test_kvstore.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
//! Tests for KV store validation requirements
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::database::mint::{
|
||||
validate_kvstore_params, validate_kvstore_string, KVSTORE_NAMESPACE_KEY_ALPHABET,
|
||||
KVSTORE_NAMESPACE_KEY_MAX_LEN,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_string_valid_inputs() {
|
||||
// Test valid strings
|
||||
assert!(validate_kvstore_string("").is_ok());
|
||||
assert!(validate_kvstore_string("abc").is_ok());
|
||||
assert!(validate_kvstore_string("ABC").is_ok());
|
||||
assert!(validate_kvstore_string("123").is_ok());
|
||||
assert!(validate_kvstore_string("test_key").is_ok());
|
||||
assert!(validate_kvstore_string("test-key").is_ok());
|
||||
assert!(validate_kvstore_string("test_KEY-123").is_ok());
|
||||
|
||||
// Test max length string
|
||||
let max_length_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN);
|
||||
assert!(validate_kvstore_string(&max_length_str).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_string_invalid_length() {
|
||||
// Test string too long
|
||||
let too_long_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN + 1);
|
||||
let result = validate_kvstore_string(&too_long_str);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("exceeds maximum length"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_string_invalid_characters() {
|
||||
// Test invalid characters
|
||||
let invalid_chars = vec![
|
||||
"test@key", // @
|
||||
"test key", // space
|
||||
"test.key", // .
|
||||
"test/key", // /
|
||||
"test\\key", // \
|
||||
"test+key", // +
|
||||
"test=key", // =
|
||||
"test!key", // !
|
||||
"test#key", // #
|
||||
"test$key", // $
|
||||
"test%key", // %
|
||||
"test&key", // &
|
||||
"test*key", // *
|
||||
"test(key", // (
|
||||
"test)key", // )
|
||||
"test[key", // [
|
||||
"test]key", // ]
|
||||
"test{key", // {
|
||||
"test}key", // }
|
||||
"test|key", // |
|
||||
"test;key", // ;
|
||||
"test:key", // :
|
||||
"test'key", // '
|
||||
"test\"key", // "
|
||||
"test<key", // <
|
||||
"test>key", // >
|
||||
"test,key", // ,
|
||||
"test?key", // ?
|
||||
"test~key", // ~
|
||||
"test`key", // `
|
||||
];
|
||||
|
||||
for invalid_str in invalid_chars {
|
||||
let result = validate_kvstore_string(invalid_str);
|
||||
assert!(result.is_err(), "Expected '{}' to be invalid", invalid_str);
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("invalid characters"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_params_valid() {
|
||||
// Test valid parameter combinations
|
||||
assert!(validate_kvstore_params("primary", "secondary", "key").is_ok());
|
||||
assert!(validate_kvstore_params("primary", "", "key").is_ok());
|
||||
assert!(validate_kvstore_params("", "", "key").is_ok());
|
||||
assert!(validate_kvstore_params("p1", "s1", "different_key").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_params_empty_namespace_rules() {
|
||||
// Test empty namespace rules: if primary is empty, secondary must be empty too
|
||||
let result = validate_kvstore_params("", "secondary", "key");
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("If primary_namespace is empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_params_collision_prevention() {
|
||||
// Test collision prevention between keys and namespaces
|
||||
let test_cases = vec![
|
||||
("primary", "secondary", "primary"), // key matches primary namespace
|
||||
("primary", "secondary", "secondary"), // key matches secondary namespace
|
||||
];
|
||||
|
||||
for (primary, secondary, key) in test_cases {
|
||||
let result = validate_kvstore_params(primary, secondary, key);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Expected collision for key '{}' with namespaces '{}'/'{}'",
|
||||
key,
|
||||
primary,
|
||||
secondary
|
||||
);
|
||||
let error_msg = result.unwrap_err().to_string();
|
||||
assert!(error_msg.contains("conflicts with namespace"));
|
||||
}
|
||||
|
||||
// Test that a combined namespace string would be invalid due to the slash character
|
||||
let result = validate_kvstore_params("primary", "secondary", "primary_secondary");
|
||||
assert!(result.is_ok(), "This should be valid - no actual collision");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_kvstore_params_invalid_strings() {
|
||||
// Test invalid characters in any parameter
|
||||
let result = validate_kvstore_params("primary@", "secondary", "key");
|
||||
assert!(result.is_err());
|
||||
|
||||
let result = validate_kvstore_params("primary", "secondary!", "key");
|
||||
assert!(result.is_err());
|
||||
|
||||
let result = validate_kvstore_params("primary", "secondary", "key with space");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alphabet_constants() {
|
||||
// Verify the alphabet constant is as expected
|
||||
assert_eq!(
|
||||
KVSTORE_NAMESPACE_KEY_ALPHABET,
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
|
||||
);
|
||||
assert_eq!(KVSTORE_NAMESPACE_KEY_MAX_LEN, 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alphabet_coverage() {
|
||||
// Test that all valid characters are actually accepted
|
||||
for ch in KVSTORE_NAMESPACE_KEY_ALPHABET.chars() {
|
||||
let test_str = ch.to_string();
|
||||
assert!(
|
||||
validate_kvstore_string(&test_str).is_ok(),
|
||||
"Character '{}' should be valid",
|
||||
ch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_namespace_segmentation_examples() {
|
||||
// Test realistic namespace segmentation scenarios
|
||||
|
||||
// Valid segmentation examples
|
||||
let valid_examples = vec![
|
||||
("wallets", "user123", "balance"),
|
||||
("quotes", "mint", "quote_12345"),
|
||||
("keysets", "", "active_keyset"),
|
||||
("", "", "global_config"),
|
||||
("auth", "session_456", "token"),
|
||||
("mint_info", "", "version"),
|
||||
];
|
||||
|
||||
for (primary, secondary, key) in valid_examples {
|
||||
assert!(
|
||||
validate_kvstore_params(primary, secondary, key).is_ok(),
|
||||
"Valid example should pass: '{}'/'{}'/'{}'",
|
||||
primary,
|
||||
secondary,
|
||||
key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_per_namespace_uniqueness() {
|
||||
// This test documents the requirement that implementations should ensure
|
||||
// per-namespace key uniqueness. The validation function doesn't enforce
|
||||
// database-level uniqueness (that's handled by the database schema),
|
||||
// but ensures naming conflicts don't occur between keys and namespaces.
|
||||
|
||||
// These should be valid (different namespaces)
|
||||
assert!(validate_kvstore_params("ns1", "sub1", "key1").is_ok());
|
||||
assert!(validate_kvstore_params("ns2", "sub1", "key1").is_ok()); // same key, different primary namespace
|
||||
assert!(validate_kvstore_params("ns1", "sub2", "key1").is_ok()); // same key, different secondary namespace
|
||||
|
||||
// These should fail (collision within namespace)
|
||||
assert!(validate_kvstore_params("ns1", "sub1", "ns1").is_err()); // key conflicts with primary namespace
|
||||
assert!(validate_kvstore_params("ns1", "sub1", "sub1").is_err()); // key conflicts with secondary namespace
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@ mod wallet;
|
||||
#[cfg(feature = "mint")]
|
||||
pub use mint::{
|
||||
Database as MintDatabase, DbTransactionFinalizer as MintDbWriterFinalizer,
|
||||
KeysDatabase as MintKeysDatabase, KeysDatabaseTransaction as MintKeyDatabaseTransaction,
|
||||
ProofsDatabase as MintProofsDatabase, ProofsTransaction as MintProofsTransaction,
|
||||
QuotesDatabase as MintQuotesDatabase, QuotesTransaction as MintQuotesTransaction,
|
||||
SignaturesDatabase as MintSignaturesDatabase,
|
||||
KVStore as MintKVStore, KVStoreDatabase as MintKVStoreDatabase,
|
||||
KVStoreTransaction as MintKVStoreTransaction, KeysDatabase as MintKeysDatabase,
|
||||
KeysDatabaseTransaction as MintKeyDatabaseTransaction, ProofsDatabase as MintProofsDatabase,
|
||||
ProofsTransaction as MintProofsTransaction, QuotesDatabase as MintQuotesDatabase,
|
||||
QuotesTransaction as MintQuotesTransaction, SignaturesDatabase as MintSignaturesDatabase,
|
||||
SignaturesTransaction as MintSignatureTransaction, Transaction as MintTransaction,
|
||||
};
|
||||
#[cfg(all(feature = "mint", feature = "auth"))]
|
||||
@@ -187,6 +188,10 @@ pub enum Error {
|
||||
/// QuoteNotFound
|
||||
#[error("Quote not found")]
|
||||
QuoteNotFound,
|
||||
|
||||
/// KV Store invalid key or namespace
|
||||
#[error("Invalid KV store key or namespace: {0}")]
|
||||
KVStoreInvalidKey(String),
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
|
||||
@@ -271,6 +271,9 @@ pub enum Error {
|
||||
/// Transaction not found
|
||||
#[error("Transaction not found")]
|
||||
TransactionNotFound,
|
||||
/// KV Store invalid key or namespace
|
||||
#[error("Invalid KV store key or namespace: {0}")]
|
||||
KVStoreInvalidKey(String),
|
||||
/// Custom Error
|
||||
#[error("`{0}`")]
|
||||
Custom(String),
|
||||
|
||||
Reference in New Issue
Block a user