Skip to content

Commit 86cb4ce

Browse files
authored
implement bill endorsements endpoint,fix cors,add endorsement count (#381)
1 parent 7a8bb0e commit 86cb4ce

File tree

8 files changed

+370
-11
lines changed

8 files changed

+370
-11
lines changed

src/blockchain/bill/block.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ impl BillBlock {
984984
}
985985
}
986986

987-
/// If the block is a non-recourse, holder-changing block (issue, endorse, sell, mint), returns
987+
/// If the block is holder-changing block (issue, endorse, sell, mint, recourse), returns
988988
/// the new holder and signer data from the block
989989
pub fn get_holder_from_block(&self, bill_keys: &BillKeys) -> Result<Option<HolderFromBlock>> {
990990
match self.op_code {
@@ -1020,6 +1020,14 @@ impl BillBlock {
10201020
signatory: block.signatory,
10211021
}))
10221022
}
1023+
Recourse => {
1024+
let block: BillRecourseBlockData = self.get_decrypted_block_bytes(bill_keys)?;
1025+
Ok(Some(HolderFromBlock {
1026+
holder: block.recoursee,
1027+
signer: block.recourser,
1028+
signatory: block.signatory,
1029+
}))
1030+
}
10231031
_ => Ok(None),
10241032
}
10251033
}

src/blockchain/bill/chain.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ impl BillBlockchain {
8989
})
9090
}
9191

92+
/// Counts the number of endorsement blocks (mint, sell, endorse, recourse)
93+
pub fn get_endorsements_count(&self) -> u64 {
94+
self.blocks
95+
.iter()
96+
.filter(|block| {
97+
matches!(
98+
block.op_code,
99+
BillOpCode::Mint
100+
| BillOpCode::Sell
101+
| BillOpCode::Endorse
102+
| BillOpCode::Recourse
103+
)
104+
})
105+
.count() as u64
106+
}
107+
92108
/// Checks if the the chain has Endorse, or Sell blocks in it
93109
pub fn has_been_endorsed_or_sold(&self) -> bool {
94110
self.blocks

src/service/bill_service.rs

Lines changed: 256 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::persistence::ContactStoreApi;
2929
use crate::service::company_service::Company;
3030
use crate::util::BcrKeys;
3131
use crate::web::data::{
32-
BillCombinedBitcoinKey, BillsFilterRole, File, LightSignedBy, PastEndorsee,
32+
BillCombinedBitcoinKey, BillsFilterRole, Endorsement, File, LightSignedBy, PastEndorsee,
3333
};
3434
use crate::web::ErrorResponse;
3535
use crate::{dht, external, persistence, util};
@@ -522,6 +522,13 @@ pub trait BillServiceApi: Send + Sync {
522522
bill_id: &str,
523523
current_identity_node_id: &str,
524524
) -> Result<Vec<PastEndorsee>>;
525+
526+
/// Returns all endorsements of the bill
527+
async fn get_endorsements(
528+
&self,
529+
bill_id: &str,
530+
current_identity_node_id: &str,
531+
) -> Result<Vec<Endorsement>>;
525532
}
526533

527534
/// The bill service is responsible for all bill-related logic and for syncing them with the dht data.
@@ -920,6 +927,7 @@ impl BillService {
920927
let first_version_bill = chain.get_first_version_bill(&bill_keys)?;
921928
let time_of_drawing = first_version_bill.signing_timestamp;
922929
let bill_participants = chain.get_all_nodes_from_bill(&bill_keys)?;
930+
let endorsements_count = chain.get_endorsements_count();
923931

924932
let mut in_recourse = false;
925933
let mut link_to_pay_recourse = "".to_string();
@@ -1071,6 +1079,7 @@ impl BillService {
10711079
files: bill.files,
10721080
active_notification,
10731081
bill_participants,
1082+
endorsements_count,
10741083
})
10751084
}
10761085

@@ -1398,6 +1407,11 @@ impl BillService {
13981407

13991408
let mut found_last_endorsing_block_for_node = false;
14001409
for block in chain.blocks().iter().rev() {
1410+
// we ignore recourse blocks, since we're only interested in previous endorsees before
1411+
// recourse
1412+
if block.op_code == BillOpCode::Recourse {
1413+
continue;
1414+
}
14011415
if let Ok(Some(holder_from_block)) = block.get_holder_from_block(bill_keys) {
14021416
// first, we search for the last non-recourse block in which we became holder
14031417
if holder_from_block.holder.node_id == *current_identity_node_id
@@ -1415,7 +1429,7 @@ impl BillService {
14151429
.or_insert(PastEndorsee {
14161430
pay_to_the_order_of: holder_from_block.holder.clone().into(),
14171431
signed: LightSignedBy {
1418-
data: holder_from_block.signer.into(),
1432+
data: holder_from_block.signer.clone().into(),
14191433
signatory: holder_from_block.signatory.map(|s| {
14201434
LightIdentityPublicData {
14211435
name: s.name,
@@ -1424,6 +1438,7 @@ impl BillService {
14241438
}),
14251439
},
14261440
signing_timestamp: block.timestamp,
1441+
signing_address: holder_from_block.signer.postal_address,
14271442
});
14281443
}
14291444
}
@@ -1438,7 +1453,7 @@ impl BillService {
14381453
.or_insert(PastEndorsee {
14391454
pay_to_the_order_of: first_version_bill.drawer.clone().into(),
14401455
signed: LightSignedBy {
1441-
data: first_version_bill.drawer.into(),
1456+
data: first_version_bill.drawer.clone().into(),
14421457
signatory: first_version_bill
14431458
.signatory
14441459
.map(|s| LightIdentityPublicData {
@@ -1447,6 +1462,7 @@ impl BillService {
14471462
}),
14481463
},
14491464
signing_timestamp: first_version_bill.signing_timestamp,
1465+
signing_address: first_version_bill.drawer.postal_address,
14501466
});
14511467
}
14521468

@@ -3052,6 +3068,55 @@ impl BillServiceApi for BillService {
30523068

30533069
self.get_past_endorsees_for_bill(&chain, &bill_keys, current_identity_node_id)
30543070
}
3071+
3072+
async fn get_endorsements(
3073+
&self,
3074+
bill_id: &str,
3075+
current_identity_node_id: &str,
3076+
) -> Result<Vec<Endorsement>> {
3077+
if !self.store.exists(bill_id).await {
3078+
return Err(Error::NotFound);
3079+
}
3080+
3081+
let chain = self.blockchain_store.get_chain(bill_id).await?;
3082+
let bill_keys = self.store.get_keys(bill_id).await?;
3083+
3084+
let bill_participants = chain.get_all_nodes_from_bill(&bill_keys)?;
3085+
// active identity is not part of the bill
3086+
if !bill_participants
3087+
.iter()
3088+
.any(|p| p == current_identity_node_id)
3089+
{
3090+
return Err(Error::NotFound);
3091+
}
3092+
3093+
let mut result: Vec<Endorsement> = vec![];
3094+
// iterate from the back to the front, collecting all endorsement blocks
3095+
for block in chain.blocks().iter().rev() {
3096+
// we ignore issue blocks, since we are only interested in endorsements
3097+
if block.op_code == BillOpCode::Issue {
3098+
continue;
3099+
}
3100+
if let Ok(Some(holder_from_block)) = block.get_holder_from_block(&bill_keys) {
3101+
result.push(Endorsement {
3102+
pay_to_the_order_of: holder_from_block.holder.clone().into(),
3103+
signed: LightSignedBy {
3104+
data: holder_from_block.signer.clone().into(),
3105+
signatory: holder_from_block
3106+
.signatory
3107+
.map(|s| LightIdentityPublicData {
3108+
name: s.name,
3109+
node_id: s.node_id,
3110+
}),
3111+
},
3112+
signing_timestamp: block.timestamp,
3113+
signing_address: holder_from_block.signer.postal_address,
3114+
});
3115+
}
3116+
}
3117+
3118+
Ok(result)
3119+
}
30553120
}
30563121

30573122
#[derive(Debug, Clone)]
@@ -3164,6 +3229,7 @@ pub struct BitcreditBillToReturn {
31643229
/// The currently active notification for this bill if any
31653230
pub active_notification: Option<Notification>,
31663231
pub bill_participants: Vec<String>,
3232+
pub endorsements_count: u64,
31673233
}
31683234

31693235
impl BitcreditBillToReturn {
@@ -6545,6 +6611,193 @@ pub mod tests {
65456611
.expect("block could not be created")
65466612
}
65476613

6614+
#[tokio::test]
6615+
async fn get_endorsements_baseline() {
6616+
let (
6617+
mut storage,
6618+
mut chain_storage,
6619+
identity_storage,
6620+
file_upload_storage,
6621+
identity_chain_store,
6622+
company_chain_store,
6623+
contact_storage,
6624+
company_storage,
6625+
) = get_storages();
6626+
let identity = get_baseline_identity();
6627+
let mut bill = get_baseline_bill("1234");
6628+
bill.drawer = IdentityPublicData::new(identity.identity.clone()).unwrap();
6629+
6630+
storage.expect_exists().returning(|_| true);
6631+
storage.expect_get_keys().returning(|_| {
6632+
Ok(BillKeys {
6633+
private_key: TEST_PRIVATE_KEY_SECP.to_owned(),
6634+
public_key: TEST_PUB_KEY_SECP.to_owned(),
6635+
})
6636+
});
6637+
chain_storage
6638+
.expect_get_chain()
6639+
.returning(move |_| Ok(get_genesis_chain(Some(bill.clone()))));
6640+
6641+
let service = get_service(
6642+
storage,
6643+
chain_storage,
6644+
identity_storage,
6645+
file_upload_storage,
6646+
identity_chain_store,
6647+
company_chain_store,
6648+
contact_storage,
6649+
company_storage,
6650+
);
6651+
6652+
let res = service
6653+
.get_endorsements("1234", &identity.identity.node_id)
6654+
.await;
6655+
assert!(res.is_ok());
6656+
assert_eq!(res.as_ref().unwrap().len(), 0);
6657+
}
6658+
6659+
#[tokio::test]
6660+
async fn get_endorsements_multi() {
6661+
let (
6662+
mut storage,
6663+
mut chain_storage,
6664+
identity_storage,
6665+
file_upload_storage,
6666+
identity_chain_store,
6667+
company_chain_store,
6668+
contact_storage,
6669+
company_storage,
6670+
) = get_storages();
6671+
let identity = get_baseline_identity();
6672+
let mut bill = get_baseline_bill("1234");
6673+
let drawer = IdentityPublicData::new_only_node_id(BcrKeys::new().get_public_key());
6674+
let mint_endorsee = IdentityPublicData::new_only_node_id(BcrKeys::new().get_public_key());
6675+
let endorse_endorsee =
6676+
IdentityPublicData::new_only_node_id(BcrKeys::new().get_public_key());
6677+
let sell_endorsee = IdentityPublicData::new_only_node_id(BcrKeys::new().get_public_key());
6678+
6679+
bill.drawer = drawer.clone();
6680+
bill.drawee = IdentityPublicData::new_only_node_id(BcrKeys::new().get_public_key());
6681+
bill.payee = IdentityPublicData::new(get_baseline_identity().identity).unwrap();
6682+
6683+
storage.expect_exists().returning(|_| true);
6684+
storage.expect_get_keys().returning(|_| {
6685+
Ok(BillKeys {
6686+
private_key: TEST_PRIVATE_KEY_SECP.to_owned(),
6687+
public_key: TEST_PUB_KEY_SECP.to_owned(),
6688+
})
6689+
});
6690+
6691+
let endorse_endorsee_clone = endorse_endorsee.clone();
6692+
let mint_endorsee_clone = mint_endorsee.clone();
6693+
let sell_endorsee_clone = sell_endorsee.clone();
6694+
6695+
chain_storage.expect_get_chain().returning(move |_| {
6696+
let now = util::date::now().timestamp() as u64;
6697+
let mut chain = get_genesis_chain(Some(bill.clone()));
6698+
6699+
// add endorse block from payee to endorsee
6700+
let endorse_block = BillBlock::create_block_for_endorse(
6701+
"1234".to_string(),
6702+
chain.get_latest_block(),
6703+
&BillEndorseBlockData {
6704+
endorsee: endorse_endorsee.clone().into(),
6705+
// endorsed by payee
6706+
endorser: IdentityPublicData::new(get_baseline_identity().identity)
6707+
.unwrap()
6708+
.into(),
6709+
signatory: None,
6710+
signing_timestamp: now + 1,
6711+
signing_address: PostalAddress::new_empty(),
6712+
},
6713+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6714+
Some(&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap()),
6715+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6716+
now + 1,
6717+
)
6718+
.unwrap();
6719+
assert!(chain.try_add_block(endorse_block));
6720+
6721+
// add sell block from endorsee to sell endorsee
6722+
let sell_block = BillBlock::create_block_for_sell(
6723+
"1234".to_string(),
6724+
chain.get_latest_block(),
6725+
&BillSellBlockData {
6726+
buyer: sell_endorsee.clone().into(),
6727+
// endorsed by endorsee
6728+
seller: endorse_endorsee.clone().into(),
6729+
currency: "sat".to_string(),
6730+
sum: 15000,
6731+
payment_address: "1234paymentaddress".to_string(),
6732+
signatory: None,
6733+
signing_timestamp: now + 2,
6734+
signing_address: PostalAddress::new_empty(),
6735+
},
6736+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6737+
Some(&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap()),
6738+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6739+
now + 2,
6740+
)
6741+
.unwrap();
6742+
assert!(chain.try_add_block(sell_block));
6743+
6744+
// add mint block from sell endorsee to mint endorsee
6745+
let mint_block = BillBlock::create_block_for_mint(
6746+
"1234".to_string(),
6747+
chain.get_latest_block(),
6748+
&BillMintBlockData {
6749+
endorsee: mint_endorsee.clone().into(),
6750+
// endorsed by sell endorsee
6751+
endorser: sell_endorsee.clone().into(),
6752+
currency: "sat".to_string(),
6753+
sum: 15000,
6754+
signatory: None,
6755+
signing_timestamp: now + 3,
6756+
signing_address: PostalAddress::new_empty(),
6757+
},
6758+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6759+
Some(&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap()),
6760+
&BcrKeys::from_private_key(TEST_PRIVATE_KEY_SECP).unwrap(),
6761+
now + 3,
6762+
)
6763+
.unwrap();
6764+
assert!(chain.try_add_block(mint_block));
6765+
6766+
Ok(chain)
6767+
});
6768+
6769+
let service = get_service(
6770+
storage,
6771+
chain_storage,
6772+
identity_storage,
6773+
file_upload_storage,
6774+
identity_chain_store,
6775+
company_chain_store,
6776+
contact_storage,
6777+
company_storage,
6778+
);
6779+
6780+
let res = service
6781+
.get_endorsements("1234", &identity.identity.node_id)
6782+
.await;
6783+
assert!(res.is_ok());
6784+
// with duplicates
6785+
assert_eq!(res.as_ref().unwrap().len(), 3);
6786+
// mint was last, so it's first
6787+
assert_eq!(
6788+
res.as_ref().unwrap()[0].pay_to_the_order_of.node_id,
6789+
mint_endorsee_clone.node_id
6790+
);
6791+
assert_eq!(
6792+
res.as_ref().unwrap()[1].pay_to_the_order_of.node_id,
6793+
sell_endorsee_clone.node_id
6794+
);
6795+
assert_eq!(
6796+
res.as_ref().unwrap()[2].pay_to_the_order_of.node_id,
6797+
endorse_endorsee_clone.node_id
6798+
);
6799+
}
6800+
65486801
#[tokio::test]
65496802
async fn get_past_endorsees_baseline() {
65506803
let (

0 commit comments

Comments
 (0)