-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathlib.rs
177 lines (157 loc) · 5.89 KB
/
lib.rs
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! A contract implementing an eSealing service.
//!
//! If you seal a file, you can prove that it was in your possession at the time
//! of sealing. The dApp flow will be:
//! - Upload a file from the computer at the front-end => register its file hash
//! in the smart contract
//! - Upload a file from the computer at the front-end => retrieve the timestamp
//! and witness (sender_account) from the smart contract to prove that the
//! witness (sender_account) was in possession of that file at that time.
//!
//! Only accounts can register a file hash. During the registration, the
//! timestamp is recorded together with the witness (sender_account) that
//! registered the file hash. Each file hash can only be registered once.
//! This is because we want to record the first time when the witness
//! (sender_account) had access to that file. Re-registering a file hash (by a
//! different witness) would not prove that the second witness is also in
//! possession of that file because the second witness could have read the
//! file hash during the initial registration transaction from the blockchain.
#![cfg_attr(not(feature = "std"), no_std)]
use concordium_std::*;
/// The different errors the contract can produce.
#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
pub enum ContractError {
/// Failed parsing the parameter.
#[from(ParseError)]
ParseParams,
/// Failed logging: Log is full.
LogFull,
/// Failed logging: Log is malformed.
LogMalformed,
/// Only accounts can register a file hash.
OnlyAccount,
/// Each file hash can only be registered once.
AlreadyRegistered,
}
/// Mapping the logging errors to ContractError.
impl From<LogError> for ContractError {
fn from(le: LogError) -> Self {
match le {
LogError::Full => Self::LogFull,
LogError::Malformed => Self::LogMalformed,
}
}
}
/// The state tracked for each file.
#[derive(Serialize, Clone, Copy, SchemaType, PartialEq, Eq, Debug)]
pub struct FileState {
/// The timestamp when this file hash was registered.
pub timestamp: Timestamp,
/// The witness (sender_account) that registered this file hash.
pub witness: AccountAddress,
}
/// The contract state.
#[derive(Serial, DeserialWithState)]
#[concordium(state_parameter = "S")]
struct State<S = StateApi> {
files: StateMap<HashSha2256, FileState, S>,
}
impl State {
/// Create a new state with no files registered.
fn new(state_builder: &mut StateBuilder) -> Self {
State {
files: state_builder.new_map(),
}
}
/// Check if a file exists.
fn file_exists(&self, file_hash: &HashSha2256) -> bool {
let file = self.files.get(file_hash);
file.is_some()
}
/// Get recorded FileState (timestamp and witness) from a specific file
/// hash.
fn get_file_state(&self, file_hash: HashSha2256) -> Option<FileState> {
self.files.get(&file_hash).map(|v| *v)
}
/// Add a new file hash (replaces existing file if present).
fn add_file(&mut self, file_hash: HashSha2256, timestamp: Timestamp, witness: AccountAddress) {
let _ = self.files.insert(file_hash, FileState {
timestamp,
witness,
});
}
}
/// Tagged events to be serialized for the event log.
#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)]
pub enum Event {
Registration(RegistrationEvent),
}
/// The RegistrationEvent is logged when a new file hash is registered.
#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)]
pub struct RegistrationEvent {
/// Hash of the file to be registered by the witness (sender_account).
pub file_hash: HashSha2256,
/// Witness (sender_account) that registered the above file hash.
pub witness: AccountAddress,
/// Timestamp when this file hash was registered in the smart contract.
pub timestamp: Timestamp,
}
/// Init function that creates this eSealing smart contract.
#[init(contract = "eSealing", event = "Event")]
fn contract_init(_ctx: &InitContext, state_builder: &mut StateBuilder) -> InitResult<State> {
Ok(State::new(state_builder))
}
/// Register a new file.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - If the file hash has already been registered.
/// - If a smart contract tries to register the file hash.
#[receive(
contract = "eSealing",
name = "registerFile",
parameter = "HashSha2256",
error = "ContractError",
mutable,
enable_logger
)]
fn register_file(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> Result<(), ContractError> {
// Ensure that only accounts can register a file.
let sender_account = match ctx.sender() {
Address::Contract(_) => bail!(ContractError::OnlyAccount),
Address::Account(account_address) => account_address,
};
let file_hash: HashSha2256 = ctx.parameter_cursor().get()?;
// Ensure that the file hash hasn't been registered so far.
ensure!(!host.state().file_exists(&file_hash), ContractError::AlreadyRegistered);
let timestamp = ctx.metadata().slot_time();
// Register the file hash.
host.state_mut().add_file(file_hash, timestamp, sender_account);
// Log the event.
logger.log(&Event::Registration(RegistrationEvent {
file_hash,
witness: sender_account,
timestamp,
}))?;
Ok(())
}
/// Get the `FileState` (timestamp and witness) of a registered file hash.
/// If the file hash has not been registered, this query returns `None`.
///
/// It rejects if:
/// - It fails to parse the parameter.
#[receive(
contract = "eSealing",
name = "getFile",
parameter = "HashSha2256",
error = "ContractError",
return_value = "Option<FileState>"
)]
fn get_file(ctx: &ReceiveContext, host: &Host<State>) -> ReceiveResult<Option<FileState>> {
let file_hash: HashSha2256 = ctx.parameter_cursor().get()?;
Ok(host.state().get_file_state(file_hash))
}