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

Feat/rpc endpoints to fetch data from key #4997

Merged
merged 34 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
26cf0da
feat: given a MARF key constructed client side, retrieve clarity value
lgalabru May 8, 2024
931f315
feat: rpc endpoint to retrieve clarity metadata
hugocaillard Jul 23, 2024
ccf60f1
docs: add get_clarity_mark_value and get_clarity_metadata documentation
hugocaillard Jul 23, 2024
26c4487
refactor: improve clarity_mark_key and clarity_metadata request parsing
hugocaillard Jul 23, 2024
0735dfa
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
hugocaillard Jul 26, 2024
5619ff2
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
hugocaillard Jul 29, 2024
71cad36
refactor: rename get clarity marf value and metadata endpoints
hugocaillard Jul 29, 2024
74ac33f
feat: get data from hash
hugocaillard Nov 5, 2024
6260c16
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
hugocaillard Nov 5, 2024
77eb102
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 7, 2024
f91f512
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 8, 2024
8c0fa20
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 8, 2024
14535c4
chore: remove needless TriePath type and use TrieHash everywhere. Al…
jcnelson Nov 8, 2024
2cdf090
chore: provide access to MARF'ed data via `MARF::get_by_hash` and `Cl…
jcnelson Nov 8, 2024
4dacdd4
chore: use new ClarityDB `get_data_from_path()` to load MARF data by …
jcnelson Nov 8, 2024
3ba07f9
Merge branch 'feat/rpc-endpoints-to-fetch-data-from-key' of https://g…
jcnelson Nov 8, 2024
4a7f9e9
chore: cargo fmt
jcnelson Nov 8, 2024
48a03bb
chore: test get_by_hash() by loading both the value in get_by_key() a…
jcnelson Nov 8, 2024
88d5595
Merge branch 'feat/rpc-endpoints-to-fetch-data-from-key' of https://g…
jcnelson Nov 8, 2024
3aef92d
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 8, 2024
878a8a3
feat: validate get clarity metadata key format
hugocaillard Nov 15, 2024
592e19f
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
hugocaillard Nov 15, 2024
f27c84f
docs: update openapi.yaml and rpc-endpoint.md
hugocaillard Nov 15, 2024
7f86352
fix: metadata key validation for cotnract analysis
hugocaillard Nov 15, 2024
9429c8e
docs: address review
hugocaillard Nov 15, 2024
74397d0
refactor: fix unbounded regex and add tests
hugocaillard Nov 18, 2024
a94abfd
test: add getclaritymetadata test case for errors
hugocaillard Nov 18, 2024
7f3191b
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 18, 2024
c9d5295
tests: add failing case
hugocaillard Nov 18, 2024
8e8fd17
chore: expand test coverage to include errors
jcnelson Nov 21, 2024
8077fb7
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
hugocaillard Nov 21, 2024
f3bd41d
tests: add rpc get marf value vm-account test case
hugocaillard Nov 21, 2024
b2bfb5e
chore: use STXBalance
jcnelson Nov 26, 2024
9d0cebd
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
jcnelson Nov 26, 2024
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
131 changes: 120 additions & 11 deletions clarity/src/vm/database/clarity_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use stacks_common::consts::{
};
use stacks_common::types::chainstate::{
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksAddress, StacksBlockId,
VRFSeed,
TrieHash, VRFSeed,
};
use stacks_common::types::{Address, StacksEpoch as GenericStacksEpoch, StacksEpochId};
use stacks_common::util::hash::{to_hex, Hash160, Sha256Sum, Sha512Trunc256Sum};
Expand Down Expand Up @@ -76,6 +76,68 @@ pub enum StoreType {
PoxUnlockHeight = 0x15,
}

impl TryFrom<&str> for StoreType {
type Error = String;

fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
use self::StoreType::*;

let hex_value = u8::from_str_radix(value, 10).map_err(|e| e.to_string())?;
match hex_value {
0x00 => Ok(DataMap),
0x01 => Ok(Variable),
0x02 => Ok(FungibleToken),
0x03 => Ok(CirculatingSupply),
0x04 => Ok(NonFungibleToken),
0x05 => Ok(DataMapMeta),
0x06 => Ok(VariableMeta),
0x07 => Ok(FungibleTokenMeta),
0x08 => Ok(NonFungibleTokenMeta),
0x09 => Ok(Contract),
0x10 => Ok(SimmedBlock),
0x11 => Ok(SimmedBlockHeight),
0x12 => Ok(Nonce),
0x13 => Ok(STXBalance),
0x14 => Ok(PoxSTXLockup),
0x15 => Ok(PoxUnlockHeight),
_ => Err("Invalid StoreType".into()),
}
}
}

pub enum ContractDataVarName {
Contract,
ContractSize,
ContractSrc,
ContractDataSize,
}

impl ContractDataVarName {
pub fn as_str(&self) -> &str {
match self {
Self::Contract => "contract",
Self::ContractSize => "contract-size",
Self::ContractSrc => "contract-src",
Self::ContractDataSize => "contract-data-size",
}
}
}

impl TryFrom<&str> for ContractDataVarName {
type Error = String;

fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
use self::ContractDataVarName::*;
match value {
"contract" => Ok(Contract),
"contract-size" => Ok(ContractSize),
"contract-src" => Ok(ContractSrc),
"contract-data-size" => Ok(ContractDataSize),
_ => Err("Invalid ContractDataVarName".into()),
}
}
}

pub struct ClarityDatabase<'a> {
pub store: RollbackWrapper<'a>,
headers_db: &'a dyn HeadersDB,
Expand Down Expand Up @@ -465,6 +527,13 @@ impl<'a> ClarityDatabase<'a> {
self.store.get_data::<T>(key)
}

pub fn get_data_by_hash<T>(&mut self, hash: &TrieHash) -> Result<Option<T>>
where
T: ClarityDeserializable<T>,
{
self.store.get_data_by_hash::<T>(hash)
}

pub fn put_value(&mut self, key: &str, value: Value, epoch: &StacksEpochId) -> Result<()> {
self.put_value_with_size(key, value, epoch)?;
Ok(())
Expand Down Expand Up @@ -522,6 +591,16 @@ impl<'a> ClarityDatabase<'a> {
self.store.get_data_with_proof(key)
}

pub fn get_data_with_proof_by_hash<T>(
&mut self,
hash: &TrieHash,
) -> Result<Option<(T, Vec<u8>)>>
where
T: ClarityDeserializable<T>,
{
self.store.get_data_with_proof_by_hash(hash)
}

pub fn make_key_for_trip(
contract_identifier: &QualifiedContractIdentifier,
data: StoreType,
Expand Down Expand Up @@ -559,12 +638,18 @@ impl<'a> ClarityDatabase<'a> {
self.store
.prepare_for_contract_metadata(contract_identifier, hash)?;
// insert contract-size
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractSize.as_str(),
);
self.insert_metadata(contract_identifier, &key, &(contract_content.len() as u64))?;

// insert contract-src
if STORE_CONTRACT_SRC_INTERFACE {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractSrc.as_str(),
);
self.insert_metadata(contract_identifier, &key, &contract_content.to_string())?;
}
Ok(())
Expand All @@ -574,7 +659,10 @@ impl<'a> ClarityDatabase<'a> {
&mut self,
contract_identifier: &QualifiedContractIdentifier,
) -> Option<String> {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractSrc.as_str(),
);
self.fetch_metadata(contract_identifier, &key)
.ok()
.flatten()
Expand Down Expand Up @@ -683,15 +771,21 @@ impl<'a> ClarityDatabase<'a> {
&mut self,
contract_identifier: &QualifiedContractIdentifier,
) -> Result<u64> {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractSize.as_str(),
);
let contract_size: u64 =
self.fetch_metadata(contract_identifier, &key)?
.ok_or_else(|| {
InterpreterError::Expect(
"Failed to read non-consensus contract metadata, even though contract exists in MARF."
.into())
})?;
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractDataSize.as_str(),
);
let data_size: u64 = self
.fetch_metadata(contract_identifier, &key)?
.ok_or_else(|| {
Expand All @@ -710,7 +804,10 @@ impl<'a> ClarityDatabase<'a> {
contract_identifier: &QualifiedContractIdentifier,
data_size: u64,
) -> Result<()> {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractSize.as_str(),
);
let contract_size: u64 =
self.fetch_metadata(contract_identifier, &key)?
.ok_or_else(|| {
Expand All @@ -720,7 +817,10 @@ impl<'a> ClarityDatabase<'a> {
})?;
contract_size.cost_overflow_add(data_size)?;

let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::ContractDataSize.as_str(),
);
self.insert_metadata(contract_identifier, &key, &data_size)?;
Ok(())
}
Expand All @@ -730,21 +830,30 @@ impl<'a> ClarityDatabase<'a> {
contract_identifier: &QualifiedContractIdentifier,
contract: Contract,
) -> Result<()> {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::Contract.as_str(),
);
self.insert_metadata(contract_identifier, &key, &contract)?;
Ok(())
}

pub fn has_contract(&mut self, contract_identifier: &QualifiedContractIdentifier) -> bool {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::Contract.as_str(),
);
self.store.has_metadata_entry(contract_identifier, &key)
}

pub fn get_contract(
&mut self,
contract_identifier: &QualifiedContractIdentifier,
) -> Result<Contract> {
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
let key = ClarityDatabase::make_metadata_key(
StoreType::Contract,
ContractDataVarName::Contract.as_str(),
);
let mut data: Contract = self.fetch_metadata(contract_identifier, &key)?
.ok_or_else(|| InterpreterError::Expect(
"Failed to read non-consensus contract metadata, even though contract exists in MARF."
Expand Down
19 changes: 18 additions & 1 deletion clarity/src/vm/database/clarity_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::path::PathBuf;

#[cfg(feature = "canonical")]
use rusqlite::Connection;
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId, VRFSeed};
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId, TrieHash, VRFSeed};
use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha512Trunc256Sum};

use crate::vm::analysis::AnalysisDatabase;
Expand Down Expand Up @@ -64,9 +64,15 @@ pub trait ClarityBackingStore {
fn put_all_data(&mut self, items: Vec<(String, String)>) -> Result<()>;
/// fetch K-V out of the committed datastore
fn get_data(&mut self, key: &str) -> Result<Option<String>>;
/// fetch Hash(K)-V out of the commmitted datastore
fn get_data_from_path(&mut self, hash: &TrieHash) -> Result<Option<String>>;
/// fetch K-V out of the committed datastore, along with the byte representation
/// of the Merkle proof for that key-value pair
fn get_data_with_proof(&mut self, key: &str) -> Result<Option<(String, Vec<u8>)>>;
fn get_data_with_proof_from_path(
&mut self,
hash: &TrieHash,
) -> Result<Option<(String, Vec<u8>)>>;
fn has_entry(&mut self, key: &str) -> Result<bool> {
Ok(self.get_data(key)?.is_some())
}
Expand Down Expand Up @@ -209,10 +215,21 @@ impl ClarityBackingStore for NullBackingStore {
panic!("NullBackingStore can't retrieve data")
}

fn get_data_from_path(&mut self, _hash: &TrieHash) -> Result<Option<String>> {
panic!("NullBackingStore can't retrieve data")
}

fn get_data_with_proof(&mut self, _key: &str) -> Result<Option<(String, Vec<u8>)>> {
panic!("NullBackingStore can't retrieve data")
}

fn get_data_with_proof_from_path(
&mut self,
_hash: &TrieHash,
) -> Result<Option<(String, Vec<u8>)>> {
panic!("NullBackingStore can't retrieve data")
}

#[cfg(feature = "canonical")]
fn get_side_store(&mut self) -> &Connection {
panic!("NullBackingStore has no side store")
Expand Down
34 changes: 33 additions & 1 deletion clarity/src/vm/database/key_value_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use std::hash::Hash;

use hashbrown::HashMap;
use stacks_common::types::chainstate::StacksBlockId;
use stacks_common::types::chainstate::{StacksBlockId, TrieHash};
use stacks_common::types::StacksEpochId;
use stacks_common::util::hash::Sha512Trunc256Sum;

Expand Down Expand Up @@ -369,6 +369,21 @@ impl<'a> RollbackWrapper<'a> {
.transpose()
}

/// this function will only return commitment proofs for values _already_ materialized
/// in the underlying store. otherwise it returns None.
pub fn get_data_with_proof_by_hash<T>(
&mut self,
hash: &TrieHash,
) -> InterpreterResult<Option<(T, Vec<u8>)>>
where
T: ClarityDeserializable<T>,
{
self.store
.get_data_with_proof_from_path(hash)?
.map(|(value, proof)| Ok((T::deserialize(&value)?, proof)))
.transpose()
}

pub fn get_data<T>(&mut self, key: &str) -> InterpreterResult<Option<T>>
where
T: ClarityDeserializable<T>,
Expand All @@ -392,6 +407,23 @@ impl<'a> RollbackWrapper<'a> {
.transpose()
}

/// DO NOT USE IN CONSENSUS CODE.
///
/// Load data directly from the underlying store, given its trie hash. The lookup map will not
/// be used.
///
/// This should never be called from within the Clarity VM, or via block-processing. It's only
/// meant to be used by the RPC system.
pub fn get_data_by_hash<T>(&mut self, hash: &TrieHash) -> InterpreterResult<Option<T>>
where
T: ClarityDeserializable<T>,
{
self.store
.get_data_from_path(hash)?
.map(|x| T::deserialize(&x))
.transpose()
}

pub fn deserialize_value(
value_hex: &str,
expected: &TypeSignature,
Expand Down
13 changes: 12 additions & 1 deletion clarity/src/vm/database/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use rusqlite::{
params, Connection, Error as SqliteError, ErrorCode as SqliteErrorCode, OptionalExtension, Row,
Savepoint,
};
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId};
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId, TrieHash};
use stacks_common::types::sqlite::NO_PARAMS;
use stacks_common::util::db::tx_busy_handler;
use stacks_common::util::hash::Sha512Trunc256Sum;
Expand Down Expand Up @@ -330,10 +330,21 @@ impl ClarityBackingStore for MemoryBackingStore {
SqliteConnection::get(self.get_side_store(), key)
}

fn get_data_from_path(&mut self, hash: &TrieHash) -> Result<Option<String>> {
SqliteConnection::get(self.get_side_store(), hash.to_string().as_str())
}

fn get_data_with_proof(&mut self, key: &str) -> Result<Option<(String, Vec<u8>)>> {
Ok(SqliteConnection::get(self.get_side_store(), key)?.map(|x| (x, vec![])))
}

fn get_data_with_proof_from_path(
&mut self,
hash: &TrieHash,
) -> Result<Option<(String, Vec<u8>)>> {
self.get_data_with_proof(&hash.to_string())
}

fn get_side_store(&mut self) -> &Connection {
&self.side_store
}
Expand Down
29 changes: 29 additions & 0 deletions docs/rpc-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,35 @@ Where data is the hex serialization of the variable value.
This endpoint also accepts a querystring parameter `?proof=` which when supplied `0`, will return the
JSON object _without_ the `proof` field.

### GET /v2/clarity/marf/[Clarity MARF Key]
Attempt to fetch the value of a MARF key. The key is identified with [Clarity MARF Key].

Returns JSON data in the form:

```json
{
"data": "0x01ce...",
"proof": "0x01ab...",
}
```

Where data is the hex serialization of the value.

### GET /v2/clarity/metadata/[Stacks Address]/[Contract Name]/[Clarity Metadata Key]
Attempt to fetch the metadata of a contract.
The contract is identified with [Stacks Address] and [Contract Name] in the URL path.
The metadata key is identified with [Clarity Metadata Key].

Returns JSON data in the form:

```json
{
"data": "'{\"contract_identifier\":{...}'",
}
```

Where data is the metadata formatted as a JSON string.

### GET /v2/constant_val/[Stacks Address]/[Contract Name]/[Constant Name]
Attempt to fetch a constant from a contract. The contract is identified with [Stacks Address] and
[Contract Name] in the URL path. The constant is identified with [Constant Name].
Expand Down
4 changes: 4 additions & 0 deletions docs/rpc/api/core-node/get-clarity-marf-value.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"data": "0x0a0c000000010a6d6f6e737465722d69640100000000000000000000000000000001",
"proof": "0x123..."
}
Loading