Commit 6b6f7986 authored by Pascal Engélibert's avatar Pascal Engélibert 🚴
Browse files

WIP feat(db+indexer): certifications

parent 03606119
Pipeline #12534 failed with stages
in 3 minutes and 59 seconds
......@@ -13,5 +13,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod cert_id;
pub mod gva_utxo_id;
pub mod wallet_hash_with_bn;
// Copyright (C) 2021 Pascal Engélibert
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct GvaCertIdKeyDbV1 {
pub issuer: U64BE,
pub target: U64BE,
}
impl Default for GvaCertIdKeyDbV1 {
fn default() -> Self {
Self {
issuer: U64BE(0),
target: U64BE(0),
}
}
}
impl std::fmt::Display for GvaCertIdKeyDbV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.issuer.0, self.target.0,)
}
}
impl AsBytes for GvaCertIdKeyDbV1 {
fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
f(&([self.issuer.0.to_be_bytes(), self.target.0.to_be_bytes()].concat()))
}
}
impl FromBytes for GvaCertIdKeyDbV1 {
type Err = CorruptedBytes;
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
Ok(Self {
issuer: U64BE::from_bytes(&bytes[0..8])
.map_err(|_| CorruptedBytes("db corrupted".to_owned()))?,
target: U64BE::from_bytes(&bytes[8..16])
.map_err(|_| CorruptedBytes("db corrupted".to_owned()))?,
})
}
}
#[cfg(feature = "explorer")]
impl ExplorableKey for GvaCertIdKeyDbV1 {
fn from_explorer_str(_: &str) -> std::result::Result<Self, FromExplorerKeyErr> {
unimplemented!()
}
fn to_explorer_string(&self) -> KvResult<String> {
Ok(self.to_string())
}
}
......@@ -25,17 +25,19 @@
mod keys;
mod values;
pub use keys::cert_id::GvaCertIdKeyDbV1;
pub use keys::gva_utxo_id::GvaUtxoIdDbV1;
pub use keys::wallet_hash_with_bn::WalletHashWithBnV1Db;
pub use values::gva_block_db::GvaBlockDbV1;
pub use values::gva_cert_db::{GvaCertDbV1, GvaCertIdDbV1, GvaExpiredCertDbV1};
pub use values::gva_idty_db::GvaIdtyDbV1;
pub use values::gva_tx::GvaTxDbV1;
pub use values::wallet_script_array::WalletScriptArrayV2;
pub use values::HashDb;
pub use values::{HashDb, PublicKeyDb};
pub(crate) use bincode::Options as _;
pub(crate) use duniter_core::common::prelude::*;
pub(crate) use duniter_core::crypto::hashs::Hash;
pub(crate) use duniter_core::crypto::{hashs::Hash, keys::ed25519::PublicKey};
pub(crate) use duniter_core::dbs::kv_typed;
pub(crate) use duniter_core::dbs::kv_typed::db_schema;
pub(crate) use duniter_core::dbs::kv_typed::prelude::*;
......@@ -57,7 +59,10 @@ db_schema!(
["blocks_with_ud", BlocksWithUd, U32BE, ()],
["blockchain_time", BlockchainTime, U32BE, u64],
["blocks_chunk_hash", BlocksChunkHash, U32BE, HashDb],
["certifications", Certifications, GvaCertIdKeyDbV1, GvaCertDbV1],
["certs_by_expire", CertsByExpire, U64BE, Vec<GvaCertIdDbV1>],
["current_blocks_chunk", CurrentBlocksChunk, U32BE, GvaBlockDbV1],
["expired_certs", ExpiredCerts, U32BE, Vec<GvaExpiredCertDbV1>],
["gva_identities", GvaIdentities, PubKeyKeyV2, GvaIdtyDbV1],
[
"gva_utxos",
......@@ -65,6 +70,7 @@ db_schema!(
GvaUtxoIdDbV1,
SourceAmountValV2
],
["idties_by_id", IdtiesById, U64BE, PublicKeyDb],
[
"scripts_by_pubkey",
ScriptsByPubkey,
......
......@@ -16,6 +16,7 @@
use crate::*;
pub mod gva_block_db;
pub mod gva_cert_db;
pub mod gva_idty_db;
pub mod gva_tx;
pub mod wallet_script_array;
......@@ -56,3 +57,40 @@ impl ExplorableValue for HashDb {
Ok(serde_json::Value::String(self.0.to_hex()))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct PublicKeyDb(pub PublicKey);
impl AsBytes for PublicKeyDb {
fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
f(self.0.as_ref())
}
}
impl kv_typed::prelude::FromBytes for PublicKeyDb {
type Err = bincode::Error;
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
let mut pubkey_bytes = [0; 32];
pubkey_bytes.copy_from_slice(bytes);
Ok(Self(PublicKey::from_32_bytes_array(pubkey_bytes)))
}
}
impl ToDumpString for PublicKeyDb {
fn to_dump_string(&self) -> String {
todo!()
}
}
#[cfg(feature = "explorer")]
impl ExplorableValue for PublicKeyDb {
fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
Ok(Self(
PublicKey::from_hex(source).map_err(|e| FromExplorerValueErr(e.into()))?,
))
}
fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
Ok(serde_json::Value::String(self.0.to_hex()))
}
}
// Copyright (C) 2021 Pascal Engélibert
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
// TODO remove repr(packed)
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Serialize,
Deserialize,
zerocopy::AsBytes,
zerocopy::FromBytes,
)]
#[repr(packed)]
pub struct GvaCertIdDbV1 {
pub issuer: usize,
pub target: usize,
}
impl GvaCertIdDbV1 {
pub fn to_key(self) -> GvaCertIdKeyDbV1 {
GvaCertIdKeyDbV1 {
issuer: U64BE(self.issuer as u64),
target: U64BE(self.target as u64),
}
}
}
impl AsBytes for GvaCertIdDbV1 {
fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
f(&bincode_db()
.serialize(&self)
.unwrap_or_else(|_| unreachable!()))
}
}
impl kv_typed::prelude::FromBytes for GvaCertIdDbV1 {
type Err = bincode::Error;
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
bincode_db().deserialize(bytes)
}
}
impl ToDumpString for GvaCertIdDbV1 {
fn to_dump_string(&self) -> String {
todo!()
}
}
#[cfg(feature = "explorer")]
impl ExplorableValue for GvaCertIdDbV1 {
fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
}
fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
}
}
// TODO remove repr(packed)
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Serialize,
Deserialize,
zerocopy::AsBytes,
zerocopy::FromBytes,
)]
#[repr(packed)]
pub struct GvaCertDbV1 {
pub written_on: BlockNumber,
}
impl AsBytes for GvaCertDbV1 {
fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
f(&bincode_db()
.serialize(&self)
.unwrap_or_else(|_| unreachable!()))
}
}
impl kv_typed::prelude::FromBytes for GvaCertDbV1 {
type Err = bincode::Error;
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
bincode_db().deserialize(bytes)
}
}
impl ToDumpString for GvaCertDbV1 {
fn to_dump_string(&self) -> String {
todo!()
}
}
#[cfg(feature = "explorer")]
impl ExplorableValue for GvaCertDbV1 {
fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
}
fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
}
}
// TODO remove repr(packed)
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Serialize,
Deserialize,
zerocopy::AsBytes,
zerocopy::FromBytes,
)]
#[repr(packed)]
pub struct GvaExpiredCertDbV1 {
pub id: GvaCertIdDbV1,
pub cert: GvaCertDbV1,
}
impl AsBytes for GvaExpiredCertDbV1 {
fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
f(&bincode_db()
.serialize(&self)
.unwrap_or_else(|_| unreachable!()))
}
}
impl kv_typed::prelude::FromBytes for GvaExpiredCertDbV1 {
type Err = bincode::Error;
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
bincode_db().deserialize(bytes)
}
}
impl ToDumpString for GvaExpiredCertDbV1 {
fn to_dump_string(&self) -> String {
todo!()
}
}
#[cfg(feature = "explorer")]
impl ExplorableValue for GvaExpiredCertDbV1 {
fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
}
fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
}
}
......@@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use std::collections::HashSet;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GvaIdtyDbV1 {
......@@ -22,6 +23,7 @@ pub struct GvaIdtyDbV1 {
pub leaves: BTreeSet<BlockNumber>,
pub first_ud: Option<BlockNumber>,
pub wot_id: WotId,
pub certifiers: HashSet<WotId>,
}
impl AsBytes for GvaIdtyDbV1 {
......
......@@ -474,6 +474,7 @@ mod tests {
use duniter_core::dbs::{databases::bc_v2::BcV2DbWritable, SourceAmountValV2, UdIdV2};
use duniter_core::{dbs::smallvec::smallvec as svec, wot::WotId};
use duniter_gva_db::GvaV1DbWritable;
use std::collections::HashSet;
#[test]
fn test_filter_blocks_numbers() -> KvResult<()> {
......@@ -483,6 +484,7 @@ mod tests {
leaves: [BlockNumber(32)].iter().copied().collect(),
first_ud: Some(BlockNumber(29)),
wot_id: WotId(0),
certifiers: HashSet::new(),
};
let blocks_with_ud = vec![
BlockNumber(3),
......@@ -533,6 +535,7 @@ mod tests {
leaves: [BlockNumber(32)].iter().copied().collect(),
first_ud: Some(BlockNumber(29)),
wot_id: WotId(0),
certifiers: HashSet::new(),
};
let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
......
// Copyright (C) 2021 Pascal Engélibert
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
pub(crate) fn update_certifications<B: Backend>(
block: &DubpBlockV10,
gva_db: &mut GvaV1DbTxRw<B::Col>,
) -> KvResult<()> {
let mut expiring_certs = Vec::new();
gva_db
.certs_by_expire
.iter(U64BE(0)..U64BE(block.common_time()), |it| {
for row in it {
if let Ok((_, cert_ids)) = row {
for cert_id in cert_ids {
// TODO error handling
if let Ok(Some(cert)) = gva_db.certifications.get(&cert_id.to_key()) {
expiring_certs.push(GvaExpiredCertDbV1 { id: cert_id, cert });
} else {
// TODO what if certification doesn't exist?
}
}
}
}
});
for cert in expiring_certs.iter() {
gva_db.certifications.remove(cert.id.to_key());
if let Some(target_pubkey) = gva_db.idties_by_id.get(&U64BE(cert.id.target as u64))? {
if let Some(mut target_idty) =
gva_db.gva_identities.get(&PubKeyKeyV2(target_pubkey.0))?
{
target_idty.certifiers.remove(&WotId(cert.id.issuer));
gva_db
.gva_identities
.upsert(PubKeyKeyV2(target_pubkey.0), target_idty);
}
}
}
if !expiring_certs.is_empty() {
gva_db
.expired_certs
.upsert(U32BE(block.number().0), expiring_certs);
}
for cert in block.certifications() {
if let (Some(issuer_idty), Some(target_idty)) = (
gva_db.gva_identities.get(&PubKeyKeyV2(cert.issuer))?,
gva_db.gva_identities.get(&PubKeyKeyV2(cert.target))?,
) {
let cert_id = GvaCertIdDbV1 {
issuer: issuer_idty.wot_id.0,
target: target_idty.wot_id.0,
};
if gva_db.certifications.get(&cert_id.to_key())?.is_none()
// TODO !contains_key
{
let mut target_idty = target_idty.clone();
target_idty.certifiers.insert(issuer_idty.wot_id);
gva_db
.gva_identities
.upsert(PubKeyKeyV2(cert.target), target_idty);
}
gva_db.certifications.upsert(
cert_id.to_key(),
GvaCertDbV1 {
written_on: block.number(),
},
);
// TODO sig_validity
if let Some(created_time) = gva_db.blockchain_time.get(&U32BE(cert.block_number.0))? {
let mut certs_same_expire = gva_db
.certs_by_expire
.get(&U64BE(created_time + 42))?
.unwrap_or_else(Vec::new);
certs_same_expire.push(cert_id);
gva_db
.certs_by_expire
.upsert(U64BE(created_time + 42), certs_same_expire);
} else {
// TODO what if the block don't exist?
}
} else {
// TODO what if idties don't exist?
}
}
Ok(())
}
pub(crate) fn revert_certifications<B: Backend>(
_block: &DubpBlockV10,
_gva_db: &mut GvaV1DbTxRw<B::Col>,
) -> KvResult<()> {
// TODO
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::identities::update_identities;
use super::*;
use duniter_core::{
crypto::{
bases::b58::ToBase58,
keys::{ed25519::Ed25519KeyPair, KeyPair},
},
documents_parser::prelude::FromStringObject,
};
use maplit::hashset;
#[test]
fn test_update_certifications() -> KvResult<()> {
let u1_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
let u2_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
let u3_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
let u4_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
let u1_b58 = u1_pubkey.to_base58();
let u2_b58 = u2_pubkey.to_base58();
let u3_b58 = u3_pubkey.to_base58();
let u4_b58 = u4_pubkey.to_base58();
let gva_db = GvaV1Db::<Mem>::open(MemConf::default())?;
let b0 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
number: 0,
median_time: 1000,
identities: vec![
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u1", u1_b58),
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u2", u2_b58),
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u3", u3_b58),
],
joiners: vec![
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u1", u1_b58),
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u2", u2_b58),
format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u3", u3_b58),
],
certifications: vec![
format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u2_b58),
format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u2_b58, u1_b58),
format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u3_b58),
],
inner_hash: Some("0000000000000000000000000000000000000000000000000000000000000000".into()),
signature: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==".into(),
hash: Some("0000000000000000000000000000000000000000000000000000000000000000".into()),
..Default::default()
}).unwrap();
(&gva_db).write(|mut db| {
db.blockchain_time.upsert(U32BE(0), b0.common_time());
update_identities::<Mem>(&b0, &mut db.gva_identities, &mut db.idties_by_id)?;
update_certifications::<Mem>(&b0, &mut db)
})?;
assert_eq!(gva_db.certifications().count()?, 3);
assert_eq!(
gva_db.certifications().get(&GvaCertIdKeyDbV1 {
issuer: U64BE(0),
target: U64BE(1)
})?,
Some(GvaCertDbV1 {
written_on: BlockNumber(0),
})
);
assert_eq!(
gva_db.certifications().get(&GvaCertIdKeyDbV1 {
issuer: U64BE(1),
target: U64BE(0)
})?,
Some(GvaCertDbV1 {
written_on: BlockNumber(0),
})
);
assert_eq!(
gva_db.certifications().get(&GvaCertIdKeyDbV1 {
issuer: U64BE(0),
target: U64BE(2)
})?,
Some(GvaCertDbV1 {
written_on: BlockNumber(0),
})
);
assert_eq!(gva_db.certs_by_expire().count()?, 1);
assert_eq!(
gva_db
.certs_by_expire()
.get(&U64BE(b0.common_time() + 42))?,
Some(vec![
GvaCertIdDbV1 {
issuer: 0,
target: 1
},
GvaCertIdDbV1 {
issuer: 1,
target: 0
},
GvaCertIdDbV1 {
issuer: 0,
target: 2
}
])
);
assert_eq!(
gva_db
.gva_identities()
.get(&PubKeyKeyV2(u1_pubkey))?
.unwrap()
.certifiers,
hashset! {WotId(1)}
);
assert_eq!(
gva_db
.gva_identities()
.get(&PubKeyKeyV2(u2_pubkey))?
.unwrap()
.certifiers,
hashset! {WotId(0)}