1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use std::collections::HashSet;
use bee_block::{
input::{Input, UtxoInput},
output::{dto::OutputDto, InputsCommitment, Output, OutputId},
payload::{
transaction::{RegularTransactionEssence, TransactionEssence, TransactionPayload},
Payload, TaggedDataPayload,
},
semantic::{semantic_validation, ConflictReason, ValidationContext},
};
use crate::{
api::{types::PreparedTransactionData, ClientBlockBuilder},
block::output::AliasId,
secret::{types::InputSigningData, SecretManageExt},
Error, Result,
};
impl<'a> ClientBlockBuilder<'a> {
pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
log::debug!("[prepare_transaction]");
let rent_structure = self.client.get_rent_structure()?;
let token_supply = self.client.get_token_supply()?;
let mut governance_transition: Option<HashSet<AliasId>> = None;
for output in &self.outputs {
output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
if let Output::Alias(x) = output {
if x.state_index() > 0 {
let output_id = self.client.alias_output_id(*x.alias_id()).await?;
let output_response = self.client.get_output(&output_id).await?;
if let OutputDto::Alias(output) = output_response.output {
if x.state_index() == output.state_index {
let mut transitions = HashSet::new();
transitions.insert(AliasId::try_from(&output.alias_id)?);
governance_transition.replace(transitions);
}
}
}
}
}
let selected_transaction_data = if self.inputs.is_some() {
self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
.await?
} else {
self.get_inputs(&rent_structure).await?
};
let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));
let mut essence = RegularTransactionEssence::builder(inputs_commitment);
let inputs = selected_transaction_data
.inputs
.iter()
.map(|i| {
Ok(Input::Utxo(UtxoInput::new(
i.output_metadata.transaction_id,
i.output_metadata.output_index,
)?))
})
.collect::<Result<Vec<Input>>>()?;
essence = essence.with_inputs(inputs);
essence = essence.with_outputs(selected_transaction_data.outputs);
if let Some(index) = self.tag.clone() {
let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
essence = essence.with_payload(Payload::TaggedData(Box::new(tagged_data_payload)));
}
let regular_essence = essence.finish(&self.client.get_protocol_parameters()?)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
pub async fn sign_transaction(&self, prepared_transaction_data: PreparedTransactionData) -> Result<Payload> {
log::debug!("[sign_transaction] {:?}", prepared_transaction_data);
let secret_manager = self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?;
let unlocks = secret_manager
.sign_transaction_essence(&prepared_transaction_data)
.await?;
let tx_payload = TransactionPayload::new(prepared_transaction_data.essence.clone(), unlocks)?;
let current_time = self.client.get_time_checked().await?;
let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?;
if conflict != ConflictReason::None {
log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload);
return Err(Error::TransactionSemantic(conflict));
}
Ok(Payload::Transaction(Box::new(tx_payload)))
}
}
pub fn verify_semantic(
input_signing_data: &[InputSigningData],
transaction: &TransactionPayload,
current_time: u32,
) -> crate::Result<ConflictReason> {
let transaction_id = transaction.id();
let TransactionEssence::Regular(essence) = transaction.essence();
let output_ids = input_signing_data
.iter()
.map(InputSigningData::output_id)
.collect::<Result<Vec<OutputId>>>()?;
let outputs = input_signing_data
.iter()
.map(|i| i.output.clone())
.collect::<Vec<Output>>();
let inputs = output_ids
.into_iter()
.zip(outputs.iter())
.collect::<Vec<(OutputId, &Output)>>();
let context = ValidationContext::new(
&transaction_id,
essence,
inputs.iter().map(|(id, input)| (id, *input)),
transaction.unlocks(),
current_time,
);
semantic_validation(context, inputs.as_slice(), transaction.unlocks()).map_err(Error::BlockError)
}