Skip to content

Commit 380a5f0

Browse files
authored
Merge pull request #4997 from stacks-network/feat/rpc-endpoints-to-fetch-data-from-key
Feat/rpc endpoints to fetch data from key
2 parents ea7f7cd + 9d0cebd commit 380a5f0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1891
-273
lines changed

clarity/src/vm/database/clarity_db.rs

Lines changed: 120 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use stacks_common::consts::{
2323
};
2424
use stacks_common::types::chainstate::{
2525
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksAddress, StacksBlockId,
26-
VRFSeed,
26+
TrieHash, VRFSeed,
2727
};
2828
use stacks_common::types::{Address, StacksEpoch as GenericStacksEpoch, StacksEpochId};
2929
use stacks_common::util::hash::{to_hex, Hash160, Sha256Sum, Sha512Trunc256Sum};
@@ -76,6 +76,68 @@ pub enum StoreType {
7676
PoxUnlockHeight = 0x15,
7777
}
7878

79+
impl TryFrom<&str> for StoreType {
80+
type Error = String;
81+
82+
fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
83+
use self::StoreType::*;
84+
85+
let hex_value = u8::from_str_radix(value, 10).map_err(|e| e.to_string())?;
86+
match hex_value {
87+
0x00 => Ok(DataMap),
88+
0x01 => Ok(Variable),
89+
0x02 => Ok(FungibleToken),
90+
0x03 => Ok(CirculatingSupply),
91+
0x04 => Ok(NonFungibleToken),
92+
0x05 => Ok(DataMapMeta),
93+
0x06 => Ok(VariableMeta),
94+
0x07 => Ok(FungibleTokenMeta),
95+
0x08 => Ok(NonFungibleTokenMeta),
96+
0x09 => Ok(Contract),
97+
0x10 => Ok(SimmedBlock),
98+
0x11 => Ok(SimmedBlockHeight),
99+
0x12 => Ok(Nonce),
100+
0x13 => Ok(STXBalance),
101+
0x14 => Ok(PoxSTXLockup),
102+
0x15 => Ok(PoxUnlockHeight),
103+
_ => Err("Invalid StoreType".into()),
104+
}
105+
}
106+
}
107+
108+
pub enum ContractDataVarName {
109+
Contract,
110+
ContractSize,
111+
ContractSrc,
112+
ContractDataSize,
113+
}
114+
115+
impl ContractDataVarName {
116+
pub fn as_str(&self) -> &str {
117+
match self {
118+
Self::Contract => "contract",
119+
Self::ContractSize => "contract-size",
120+
Self::ContractSrc => "contract-src",
121+
Self::ContractDataSize => "contract-data-size",
122+
}
123+
}
124+
}
125+
126+
impl TryFrom<&str> for ContractDataVarName {
127+
type Error = String;
128+
129+
fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
130+
use self::ContractDataVarName::*;
131+
match value {
132+
"contract" => Ok(Contract),
133+
"contract-size" => Ok(ContractSize),
134+
"contract-src" => Ok(ContractSrc),
135+
"contract-data-size" => Ok(ContractDataSize),
136+
_ => Err("Invalid ContractDataVarName".into()),
137+
}
138+
}
139+
}
140+
79141
pub struct ClarityDatabase<'a> {
80142
pub store: RollbackWrapper<'a>,
81143
headers_db: &'a dyn HeadersDB,
@@ -465,6 +527,13 @@ impl<'a> ClarityDatabase<'a> {
465527
self.store.get_data::<T>(key)
466528
}
467529

530+
pub fn get_data_by_hash<T>(&mut self, hash: &TrieHash) -> Result<Option<T>>
531+
where
532+
T: ClarityDeserializable<T>,
533+
{
534+
self.store.get_data_by_hash::<T>(hash)
535+
}
536+
468537
pub fn put_value(&mut self, key: &str, value: Value, epoch: &StacksEpochId) -> Result<()> {
469538
self.put_value_with_size(key, value, epoch)?;
470539
Ok(())
@@ -522,6 +591,16 @@ impl<'a> ClarityDatabase<'a> {
522591
self.store.get_data_with_proof(key)
523592
}
524593

594+
pub fn get_data_with_proof_by_hash<T>(
595+
&mut self,
596+
hash: &TrieHash,
597+
) -> Result<Option<(T, Vec<u8>)>>
598+
where
599+
T: ClarityDeserializable<T>,
600+
{
601+
self.store.get_data_with_proof_by_hash(hash)
602+
}
603+
525604
pub fn make_key_for_trip(
526605
contract_identifier: &QualifiedContractIdentifier,
527606
data: StoreType,
@@ -559,12 +638,18 @@ impl<'a> ClarityDatabase<'a> {
559638
self.store
560639
.prepare_for_contract_metadata(contract_identifier, hash)?;
561640
// insert contract-size
562-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
641+
let key = ClarityDatabase::make_metadata_key(
642+
StoreType::Contract,
643+
ContractDataVarName::ContractSize.as_str(),
644+
);
563645
self.insert_metadata(contract_identifier, &key, &(contract_content.len() as u64))?;
564646

565647
// insert contract-src
566648
if STORE_CONTRACT_SRC_INTERFACE {
567-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
649+
let key = ClarityDatabase::make_metadata_key(
650+
StoreType::Contract,
651+
ContractDataVarName::ContractSrc.as_str(),
652+
);
568653
self.insert_metadata(contract_identifier, &key, &contract_content.to_string())?;
569654
}
570655
Ok(())
@@ -574,7 +659,10 @@ impl<'a> ClarityDatabase<'a> {
574659
&mut self,
575660
contract_identifier: &QualifiedContractIdentifier,
576661
) -> Option<String> {
577-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
662+
let key = ClarityDatabase::make_metadata_key(
663+
StoreType::Contract,
664+
ContractDataVarName::ContractSrc.as_str(),
665+
);
578666
self.fetch_metadata(contract_identifier, &key)
579667
.ok()
580668
.flatten()
@@ -683,15 +771,21 @@ impl<'a> ClarityDatabase<'a> {
683771
&mut self,
684772
contract_identifier: &QualifiedContractIdentifier,
685773
) -> Result<u64> {
686-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
774+
let key = ClarityDatabase::make_metadata_key(
775+
StoreType::Contract,
776+
ContractDataVarName::ContractSize.as_str(),
777+
);
687778
let contract_size: u64 =
688779
self.fetch_metadata(contract_identifier, &key)?
689780
.ok_or_else(|| {
690781
InterpreterError::Expect(
691782
"Failed to read non-consensus contract metadata, even though contract exists in MARF."
692783
.into())
693784
})?;
694-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
785+
let key = ClarityDatabase::make_metadata_key(
786+
StoreType::Contract,
787+
ContractDataVarName::ContractDataSize.as_str(),
788+
);
695789
let data_size: u64 = self
696790
.fetch_metadata(contract_identifier, &key)?
697791
.ok_or_else(|| {
@@ -710,7 +804,10 @@ impl<'a> ClarityDatabase<'a> {
710804
contract_identifier: &QualifiedContractIdentifier,
711805
data_size: u64,
712806
) -> Result<()> {
713-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
807+
let key = ClarityDatabase::make_metadata_key(
808+
StoreType::Contract,
809+
ContractDataVarName::ContractSize.as_str(),
810+
);
714811
let contract_size: u64 =
715812
self.fetch_metadata(contract_identifier, &key)?
716813
.ok_or_else(|| {
@@ -720,7 +817,10 @@ impl<'a> ClarityDatabase<'a> {
720817
})?;
721818
contract_size.cost_overflow_add(data_size)?;
722819

723-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
820+
let key = ClarityDatabase::make_metadata_key(
821+
StoreType::Contract,
822+
ContractDataVarName::ContractDataSize.as_str(),
823+
);
724824
self.insert_metadata(contract_identifier, &key, &data_size)?;
725825
Ok(())
726826
}
@@ -730,21 +830,30 @@ impl<'a> ClarityDatabase<'a> {
730830
contract_identifier: &QualifiedContractIdentifier,
731831
contract: Contract,
732832
) -> Result<()> {
733-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
833+
let key = ClarityDatabase::make_metadata_key(
834+
StoreType::Contract,
835+
ContractDataVarName::Contract.as_str(),
836+
);
734837
self.insert_metadata(contract_identifier, &key, &contract)?;
735838
Ok(())
736839
}
737840

738841
pub fn has_contract(&mut self, contract_identifier: &QualifiedContractIdentifier) -> bool {
739-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
842+
let key = ClarityDatabase::make_metadata_key(
843+
StoreType::Contract,
844+
ContractDataVarName::Contract.as_str(),
845+
);
740846
self.store.has_metadata_entry(contract_identifier, &key)
741847
}
742848

743849
pub fn get_contract(
744850
&mut self,
745851
contract_identifier: &QualifiedContractIdentifier,
746852
) -> Result<Contract> {
747-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
853+
let key = ClarityDatabase::make_metadata_key(
854+
StoreType::Contract,
855+
ContractDataVarName::Contract.as_str(),
856+
);
748857
let mut data: Contract = self.fetch_metadata(contract_identifier, &key)?
749858
.ok_or_else(|| InterpreterError::Expect(
750859
"Failed to read non-consensus contract metadata, even though contract exists in MARF."

clarity/src/vm/database/clarity_store.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::path::PathBuf;
1818

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

2424
use crate::vm::analysis::AnalysisDatabase;
@@ -64,9 +64,15 @@ pub trait ClarityBackingStore {
6464
fn put_all_data(&mut self, items: Vec<(String, String)>) -> Result<()>;
6565
/// fetch K-V out of the committed datastore
6666
fn get_data(&mut self, key: &str) -> Result<Option<String>>;
67+
/// fetch Hash(K)-V out of the commmitted datastore
68+
fn get_data_from_path(&mut self, hash: &TrieHash) -> Result<Option<String>>;
6769
/// fetch K-V out of the committed datastore, along with the byte representation
6870
/// of the Merkle proof for that key-value pair
6971
fn get_data_with_proof(&mut self, key: &str) -> Result<Option<(String, Vec<u8>)>>;
72+
fn get_data_with_proof_from_path(
73+
&mut self,
74+
hash: &TrieHash,
75+
) -> Result<Option<(String, Vec<u8>)>>;
7076
fn has_entry(&mut self, key: &str) -> Result<bool> {
7177
Ok(self.get_data(key)?.is_some())
7278
}
@@ -209,10 +215,21 @@ impl ClarityBackingStore for NullBackingStore {
209215
panic!("NullBackingStore can't retrieve data")
210216
}
211217

218+
fn get_data_from_path(&mut self, _hash: &TrieHash) -> Result<Option<String>> {
219+
panic!("NullBackingStore can't retrieve data")
220+
}
221+
212222
fn get_data_with_proof(&mut self, _key: &str) -> Result<Option<(String, Vec<u8>)>> {
213223
panic!("NullBackingStore can't retrieve data")
214224
}
215225

226+
fn get_data_with_proof_from_path(
227+
&mut self,
228+
_hash: &TrieHash,
229+
) -> Result<Option<(String, Vec<u8>)>> {
230+
panic!("NullBackingStore can't retrieve data")
231+
}
232+
216233
#[cfg(feature = "canonical")]
217234
fn get_side_store(&mut self) -> &Connection {
218235
panic!("NullBackingStore has no side store")

clarity/src/vm/database/key_value_wrapper.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use std::hash::Hash;
1818

1919
use hashbrown::HashMap;
20-
use stacks_common::types::chainstate::StacksBlockId;
20+
use stacks_common::types::chainstate::{StacksBlockId, TrieHash};
2121
use stacks_common::types::StacksEpochId;
2222
use stacks_common::util::hash::Sha512Trunc256Sum;
2323

@@ -369,6 +369,21 @@ impl<'a> RollbackWrapper<'a> {
369369
.transpose()
370370
}
371371

372+
/// this function will only return commitment proofs for values _already_ materialized
373+
/// in the underlying store. otherwise it returns None.
374+
pub fn get_data_with_proof_by_hash<T>(
375+
&mut self,
376+
hash: &TrieHash,
377+
) -> InterpreterResult<Option<(T, Vec<u8>)>>
378+
where
379+
T: ClarityDeserializable<T>,
380+
{
381+
self.store
382+
.get_data_with_proof_from_path(hash)?
383+
.map(|(value, proof)| Ok((T::deserialize(&value)?, proof)))
384+
.transpose()
385+
}
386+
372387
pub fn get_data<T>(&mut self, key: &str) -> InterpreterResult<Option<T>>
373388
where
374389
T: ClarityDeserializable<T>,
@@ -392,6 +407,23 @@ impl<'a> RollbackWrapper<'a> {
392407
.transpose()
393408
}
394409

410+
/// DO NOT USE IN CONSENSUS CODE.
411+
///
412+
/// Load data directly from the underlying store, given its trie hash. The lookup map will not
413+
/// be used.
414+
///
415+
/// This should never be called from within the Clarity VM, or via block-processing. It's only
416+
/// meant to be used by the RPC system.
417+
pub fn get_data_by_hash<T>(&mut self, hash: &TrieHash) -> InterpreterResult<Option<T>>
418+
where
419+
T: ClarityDeserializable<T>,
420+
{
421+
self.store
422+
.get_data_from_path(hash)?
423+
.map(|x| T::deserialize(&x))
424+
.transpose()
425+
}
426+
395427
pub fn deserialize_value(
396428
value_hex: &str,
397429
expected: &TypeSignature,

clarity/src/vm/database/sqlite.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rusqlite::{
1919
params, Connection, Error as SqliteError, ErrorCode as SqliteErrorCode, OptionalExtension, Row,
2020
Savepoint,
2121
};
22-
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId};
22+
use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId, TrieHash};
2323
use stacks_common::types::sqlite::NO_PARAMS;
2424
use stacks_common::util::db::tx_busy_handler;
2525
use stacks_common::util::hash::Sha512Trunc256Sum;
@@ -330,10 +330,21 @@ impl ClarityBackingStore for MemoryBackingStore {
330330
SqliteConnection::get(self.get_side_store(), key)
331331
}
332332

333+
fn get_data_from_path(&mut self, hash: &TrieHash) -> Result<Option<String>> {
334+
SqliteConnection::get(self.get_side_store(), hash.to_string().as_str())
335+
}
336+
333337
fn get_data_with_proof(&mut self, key: &str) -> Result<Option<(String, Vec<u8>)>> {
334338
Ok(SqliteConnection::get(self.get_side_store(), key)?.map(|x| (x, vec![])))
335339
}
336340

341+
fn get_data_with_proof_from_path(
342+
&mut self,
343+
hash: &TrieHash,
344+
) -> Result<Option<(String, Vec<u8>)>> {
345+
self.get_data_with_proof(&hash.to_string())
346+
}
347+
337348
fn get_side_store(&mut self) -> &Connection {
338349
&self.side_store
339350
}

docs/rpc-endpoints.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,35 @@ Where data is the hex serialization of the variable value.
172172
This endpoint also accepts a querystring parameter `?proof=` which when supplied `0`, will return the
173173
JSON object _without_ the `proof` field.
174174

175+
### GET /v2/clarity/marf/[Clarity MARF Key]
176+
Attempt to fetch the value of a MARF key. The key is identified with [Clarity MARF Key].
177+
178+
Returns JSON data in the form:
179+
180+
```json
181+
{
182+
"data": "0x01ce...",
183+
"proof": "0x01ab...",
184+
}
185+
```
186+
187+
Where data is the hex serialization of the value.
188+
189+
### GET /v2/clarity/metadata/[Stacks Address]/[Contract Name]/[Clarity Metadata Key]
190+
Attempt to fetch the metadata of a contract.
191+
The contract is identified with [Stacks Address] and [Contract Name] in the URL path.
192+
The metadata key is identified with [Clarity Metadata Key].
193+
194+
Returns JSON data in the form:
195+
196+
```json
197+
{
198+
"data": "'{\"contract_identifier\":{...}'",
199+
}
200+
```
201+
202+
Where data is the metadata formatted as a JSON string.
203+
175204
### GET /v2/constant_val/[Stacks Address]/[Contract Name]/[Constant Name]
176205
Attempt to fetch a constant from a contract. The contract is identified with [Stacks Address] and
177206
[Contract Name] in the URL path. The constant is identified with [Constant Name].
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"data": "0x0a0c000000010a6d6f6e737465722d69640100000000000000000000000000000001",
3+
"proof": "0x123..."
4+
}

0 commit comments

Comments
 (0)