This repository has been archived by the owner on Feb 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 269
/
action.rs
432 lines (354 loc) · 14.8 KB
/
action.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
use crate::{
agent::state::AgentState,
dht::{
actions::remove_queued_holding_workflow::HoldingWorkflowQueueing,
dht_store::HoldAspectAttemptId, pending_validations::PendingValidation,
},
network::{
direct_message::DirectMessage,
entry_aspect::EntryAspect,
entry_with_header::EntryWithHeader,
query::{GetLinksNetworkQuery, NetworkQueryResult},
state::NetworkState,
},
nucleus::{
actions::{call_zome_function::ExecuteZomeFnResponse, initialize::Initialization},
state::NucleusState,
HdkFnCall, HdkFnCallResult, ZomeFnCall,
},
state::State,
};
use holochain_core_types::{
chain_header::ChainHeader, crud_status::CrudStatus, dna::Dna, entry::Entry,
signature::Provenance, validation::ValidationPackage,
};
use holochain_net::{connection::net_connection::NetHandler, p2p_config::P2pConfig};
use holochain_persistence_api::cas::content::Address;
use lib3h_protocol::data_types::{EntryListData, FetchEntryData, QueryEntryData};
use std::{
hash::{Hash, Hasher},
time::{Duration, SystemTime},
vec::Vec,
};
/// Wrapper for actions that provides a unique ID
/// The unique ID is needed for state tracking to ensure that we can differentiate between two
/// Action dispatches containing the same value when doing "time travel debug".
/// The standard approach is to drop the ActionWrapper into the key of a state history HashMap and
/// use the convenience unwrap_to! macro to extract the action data in a reducer.
/// All reducer functions must accept an ActionWrapper so all dispatchers take an ActionWrapper.
#[derive(Clone, Debug, Serialize)]
pub struct ActionWrapper {
action: Action,
id: String,
}
impl ActionWrapper {
/// constructor from &Action
/// internal unique ID is automatically set
pub fn new(a: Action) -> Self {
ActionWrapper {
action: a,
// auto generate id
id: nanoid::simple(),
}
}
/// read only access to action
pub fn action(&self) -> &Action {
&self.action
}
/// read only access to id
pub fn id(&self) -> &String {
&self.id
}
}
impl PartialEq for ActionWrapper {
fn eq(&self, other: &ActionWrapper) -> bool {
self.id == other.id
}
}
impl Eq for ActionWrapper {}
impl Hash for ActionWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
///This describes a key for the actions
#[derive(Clone, PartialEq, Debug, Serialize, Eq, Hash)]
pub enum QueryKey {
Entry(GetEntryKey),
Links(GetLinksKey),
}
///This is a payload for the Get Method
#[derive(Clone, PartialEq, Debug, Serialize)]
pub enum QueryPayload {
Entry,
Links((Option<CrudStatus>, GetLinksNetworkQuery)),
}
/// All Actions for the Holochain Instance Store, according to Redux pattern.
#[derive(Clone, PartialEq, Debug, Serialize)]
#[serde(tag = "action_type", content = "data")]
#[allow(clippy::large_enum_variant)]
pub enum Action {
/// Get rid of stale information that we should drop to not have the state grow infinitely.
Prune,
ClearActionResponse(String),
// ----------------
// Agent actions:
// ----------------
/// Writes an entry to the source chain.
/// Does not validate, assumes entry is valid.
Commit((Entry, Option<Address>, Vec<Provenance>)),
// -------------
// DHT actions:
// -------------
/// Adds a holding workflow (=PendingValidation) to the queue.
/// With optional delay where the SystemTime is the time when the action got dispatched
/// and the Duration is the delay added to that time.
QueueHoldingWorkflow((PendingValidation, Option<(SystemTime, Duration)>)),
/// Removes the given item from the holding queue.
RemoveQueuedHoldingWorkflow((HoldingWorkflowQueueing, PendingValidation)),
/// Adds an entry aspect to the local DHT shard.
/// Does not validate, assumes referenced entry is valid.
HoldAspect((EntryAspect, HoldAspectAttemptId)),
//action for updating crudstatus
CrudStatus((EntryWithHeader, CrudStatus)),
// ----------------
// Network actions:
// ----------------
/// Create a network proxy instance from the given [NetworkSettings](struct.NetworkSettings.html)
InitNetwork(NetworkSettings),
/// Shut down network by sending JsonProtocoll::UntrackDna, stopping network thread and dropping P2pNetwork instance
ShutdownNetwork,
/// Makes the network PUT the given entry to the DHT.
/// Distinguishes between different entry types and does
/// the right thing respectively.
/// (only publish for AppEntryType, publish and publish_meta for links etc)
Publish(Address),
/// Publish to the network the header entry for the entry at the given address.
/// Note that the given address is that of the entry NOT the address of the header itself
PublishHeaderEntry(Address),
/// Performs a Network Query Action based on the key and payload, used for links and Entries.
/// Includes the timeout information: system time of dispatch and duration until it timeouts.
Query((QueryKey, QueryPayload, Option<(SystemTime, Duration)>)),
///Performs a Query Timeout Action which times out the query given by the key.
QueryTimeout(QueryKey),
/// Lets the network module respond to a Query request.
/// Triggered from the corresponding workflow after retrieving the
/// requested object from the DHT
RespondQuery((QueryEntryData, NetworkQueryResult)),
/// We got a response for our get request which needs to be added to the state.
/// Triggered from the network handler.
HandleQuery((NetworkQueryResult, QueryKey)),
/// Clean up the query result so the state doesn't grow indefinitely.
ClearQueryResult(QueryKey),
RespondFetch((FetchEntryData, Vec<EntryAspect>)),
/// Makes the network module send a direct (node-to-node) message
/// to the address given in [DirectMessageData](struct.DirectMessageData.html)
/// Includes the timeout information: system time of dispatch and duration until it timeouts.
SendDirectMessage((DirectMessageData, Option<(SystemTime, Duration)>)),
/// Makes the direct message connection with the given ID timeout by adding an
/// Err(HolochainError::Timeout) to NetworkState::custom_direct_message_replys.
SendDirectMessageTimeout(String),
/// Makes the network module forget about the direct message
/// connection with the given ID.
/// Triggered when we got an answer to our initial DM.
ResolveDirectConnection(String),
/// Makes the network module DM the source of the given entry
/// and prepare for receiveing an answer
GetValidationPackage((ValidationKey, ChainHeader)),
/// Makes the get validation request with the given ID timeout by adding an
/// Err(HolochainError::Timeout) to NetworkState::get_validation_package_results.
GetValidationPackageTimeout(ValidationKey),
/// Updates the state to hold the response that we got for
/// our previous request for a validation package.
/// Triggered from the network handler when we get the response.
HandleGetValidationPackage((Address, ValidationKey, Option<ValidationPackage>)),
/// Clean up the validation package result so the state doesn't grow indefinitely.
ClearValidationPackageResult(ValidationKey),
/// Updates the state to hold the response that we got for
/// our previous custom direct message.
/// Triggered from the network handler when we get the response.
HandleCustomSendResponse((String, Result<String, String>)),
/// Clean up the custom send response result so the state doesn't grow indefinitely.
ClearCustomSendResponse(String),
/// Sends the given data as JsonProtocol::HandleGetAuthoringEntryListResult
RespondAuthoringList(EntryListData),
/// Sends the given data as JsonProtocol::HandleGetGossipEntryListResult
RespondGossipList(EntryListData),
// ----------------
// Nucleus actions:
// ----------------
/// initialize a chain from Dna
/// not the same as init
/// may call init internally
InitializeChain(Dna),
/// return the result of an InitializeChain action
/// the result is an initialization structure which include the generated public token if any
ReturnInitializationResult(Result<Initialization, String>),
/// Gets dispatched when a zome function call starts.
QueueZomeFunctionCall(ZomeFnCall),
/// return the result of a zome WASM function call
ReturnZomeFunctionResult(ExecuteZomeFnResponse),
/// Let the State track that a zome call has called an HDK function
TraceInvokeHdkFunction((ZomeFnCall, HdkFnCall)),
/// Let the State track that an HDK function called by a zome call has returned
TraceReturnHdkFunction((ZomeFnCall, HdkFnCall, HdkFnCallResult)),
/// Remove all traces of the given call from state (mainly the result)
ClearZomeFunctionCall(ZomeFnCall),
/// No-op, used to check if an action channel is still open
Ping,
}
/// function signature for action handler functions
// @TODO merge these into a single signature
// @see https://github.com/holochain/holochain-rust/issues/194
pub type AgentReduceFn = ReduceFn<AgentState>;
pub type NetworkReduceFn = ReduceFn<NetworkState>;
pub type NucleusReduceFn = ReduceFn<NucleusState>;
pub type ReduceFn<S> = fn(&mut S, &State, &ActionWrapper);
/// The unique key that represents a GetLinks request, used to associate the eventual
/// response with this GetLinks request
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
pub struct GetLinksKey {
/// The address of the Link base
pub base_address: Address,
/// The link type
pub link_type: Option<String>,
/// The link tag, None means get all the tags for a given type
pub tag: Option<String>,
/// A unique ID that is used to pair the eventual result to this request
pub id: String,
}
/// The unique key that represents a Get request, used to associate the eventual
/// response with this Get request
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
pub struct GetEntryKey {
/// The address of the entry to get
pub address: Address,
/// A unique ID that is used to pair the eventual result to this request
pub id: String,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct ValidationKey {
/// The address of the entry to get the package for
pub address: Address,
/// A unique ID that is used to pair the eventual result to this request
pub id: String,
}
/// Everything the network module needs to know in order to send a
/// direct message.
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct DirectMessageData {
/// The address of the node to send a message to
pub address: Address,
/// The message itself
pub message: DirectMessage,
/// A unique message ID that is used to identify the response and attribute
/// it to the right context
pub msg_id: String,
/// Should be true if we are responding to a previous message with this message.
/// msg_id should then be the same as the in the message that we received.
pub is_response: bool,
}
/// Everything the network needs to initialize
#[derive(Clone, PartialEq, Debug, Serialize)]
pub struct NetworkSettings {
/// P2pConfig that gets passed to [P2pNetwork](struct.P2pNetwork.html)
/// determines how to connect to the network module.
pub p2p_config: P2pConfig,
/// DNA address is needed so the network module knows which network to
/// connect us to.
pub dna_address: Address,
/// The network module needs to know who we are.
/// This is this agent's address.
pub agent_id: String,
/// This is a closure of the code that gets called by the network
/// module to have us process incoming messages
pub handler: NetHandler,
}
#[cfg(test)]
pub mod tests {
use crate::{
action::{Action, ActionWrapper, GetEntryKey, QueryKey, QueryPayload},
nucleus::tests::test_call_response,
};
use holochain_core_types::entry::{expected_entry_address, test_entry};
use test_utils::calculate_hash;
/// dummy action
pub fn test_action() -> Action {
Action::Query((
QueryKey::Entry(GetEntryKey {
address: expected_entry_address(),
id: String::from("test-id"),
}),
QueryPayload::Entry,
None,
))
}
/// dummy action wrapper with test_action()
pub fn test_action_wrapper() -> ht::SpanWrap<ActionWrapper> {
ht::noop("test-noop".into()).wrap(ActionWrapper::new(test_action()))
}
/// dummy action wrapper with commit of test_entry()
pub fn test_action_wrapper_commit() -> ht::SpanWrap<ActionWrapper> {
ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::Commit((
test_entry(),
None,
vec![],
))))
}
/// dummy action for a get of test_hash()
pub fn test_action_wrapper_get() -> ht::SpanWrap<ActionWrapper> {
ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::Query((
QueryKey::Entry(GetEntryKey {
address: expected_entry_address(),
id: nanoid::simple(),
}),
QueryPayload::Entry,
None,
))))
}
pub fn test_action_wrapper_rzfr() -> ht::SpanWrap<ActionWrapper> {
ht::noop("test-noop".into()).wrap(ActionWrapper::new(Action::ReturnZomeFunctionResult(
test_call_response(),
)))
}
#[test]
/// smoke test actions
fn new_action() {
let a1 = test_action();
let a2 = test_action();
// unlike actions and wrappers, signals are equal to themselves
assert_eq!(a1, a2);
}
#[test]
/// tests that new action wrappers take an action and ensure uniqueness
fn new_action_wrapper() {
let aw1 = test_action_wrapper();
let aw2 = test_action_wrapper();
assert_eq!(aw1.data, aw1.data);
assert_ne!(aw1.data, aw2.data);
}
#[test]
/// tests read access to actions
fn action_wrapper_action() {
let aw1 = test_action_wrapper();
let aw2 = test_action_wrapper();
assert_eq!(aw1.action(), aw2.action());
assert_eq!(aw1.action(), &test_action());
}
#[test]
/// tests read access to action wrapper ids
fn action_wrapper_id() {
// can't set the ID directly (by design)
// at least test that IDs are unique, and that hitting the id() method doesn't error
let aw1 = test_action_wrapper();
let aw2 = test_action_wrapper();
assert_ne!(aw1.id(), aw2.id());
}
#[test]
/// tests that action wrapper hashes are unique
fn action_wrapper_hash() {
let aw1 = test_action_wrapper();
let aw2 = test_action_wrapper();
assert_ne!(calculate_hash(&aw1.data), calculate_hash(&aw2.data));
}
}