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
//! Helper Functions and Types that are used in other Packages of the this Workspace

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

// #[cfg(feature = "std")]
// extern crate chainblocks;

// TODO Review - maybe rename this module to `sp_protos` since we have a crate with the same name `protos` (https://github.com/fragcolor-xyz/protos)
/// Types that will be used by the Protos pallet
pub mod protos;
// TODO Review - maybe rename this module `sp_fragments` since we have a crate with the same name `fragments` (https://github.com/fragcolor-xyz/fragments)  - although it's not being used
/// Types that will be used by the Fragments pallet
pub mod fragments;

use codec::{Decode, Encode, Error as CodecError};
use sp_core::offchain::{HttpRequestStatus, Timestamp};
use sp_io::{hashing::blake2_256, offchain};
use sp_std::{
	vec::Vec
};

/// 64 bytes u8-Array
pub type Hash64 = [u8; 8];
/// 128 bytes u8-Array
pub type Hash128 = [u8; 16];
/// 256 bytes u8-Array
pub type Hash256 = [u8; 32];

/// The IPFS CID prefix used to use to obtain any data that is stored on the Fragnova Blockchain
///
/// The format of the CID prefix is: <cid-version><multicodec><multihash> (see: https://proto.school/anatomy-of-a-cid/05)
///
/// 0x01 stands for CID v1.
/// 0x55 is the Multicodec code for raw (https://github.com/multiformats/multicodec)
/// 0xa0e402 is the Multihash code for blake2b-256 (https://github.com/multiformats/multihash)
/// 0x20 is the length of the digest
pub const CID_PREFIX: [u8; 6] = hex_literal::hex!("0155a0e40220");

#[cfg(feature = "std")]
mod details {
	use lazy_static::lazy_static;
	use std::sync::Mutex;

	lazy_static! {
		pub static ref GETH_URL: Mutex<Option<Vec<u8>>> = Mutex::new(None);
	}

	// lazy_static! {
	// 	static ref FETCH_EXTRINSIC: Mutex<Option<Box<dyn Fn(&Hash256) -> Option<Vec<u8>>>>> =
	// 		Mutex::new(None);
	// }

	// use std::{convert::TryInto, sync::Mutex};

	// use chainblocks::{
	// 	cbl_env,
	// 	core::destroyVar,
	// 	types::{ChainRef, ExternalVar, Node},
	// };

	pub fn _say_hello_world(_data: &str) {
		// lazy_static! {
		// 	static ref VAR: Mutex<ExternalVar> = Mutex::new(ExternalVar::default());
		// 	static ref NODE: Node = {
		// 		let node = Node::default();
		// 		// let mut chain_var = cbl_env!("(defloop test (Msg \"Hello\"))");
		// 		let mut chain_var = cbl_env!("(Chain \"test\" :Looped .text (ExpectString) (Log))");
		// 		let chain: ChainRef = chain_var.try_into().unwrap();
		// 		chain.set_external("text", &VAR.lock().unwrap());
		// 		node.schedule(chain);
		// 		destroyVar(&mut chain_var);
		// 		node
		// 	};
		// }
		// VAR.lock().unwrap().update(data);
		// NODE.tick();
	}

	/// Get the URL of the Fragnova-owned Geth Node
	pub fn _get_geth_url() -> Option<Vec<u8>> {
		if let Some(geth_url) = GETH_URL.lock().unwrap().as_ref() {
			// well, we are doing an allocation every time we call this function here...
			Some(geth_url.clone())
		} else {
			None
		}
	}
}

#[cfg(not(feature = "std"))]
mod details {
	use super::*;

	/// TODO: Remove
	pub fn _say_hello_world(data: &str) {}

	/// TODO
	pub fn _fetch_extrinsic(hash: &Hash256) -> Option<Vec<u8>> {
		None
	}

	/// TODO
	pub fn _get_geth_url() -> Option<Vec<u8>> {
		None
	}
}

/// A runtime interface for the Fragnova Blockchain
///
/// Background:
///
/// `#[sp_runtime_interface::runtime_interface]` is an attribute macro for transforming a trait declaration into a runtime interface.
///
/// A runtime interface is a fixed interface between a Substrate compatible runtime and the native node.
/// This interface is callable from a native and a wasm runtime.
/// The **macro** will **generate** the **corresponding code for the native implementation** and the **code for calling from the wasm side to the native implementation**.
/// The macro expects the runtime interface declaration as trait declaration.
///
/// Source: https://paritytech.github.io/substrate/latest/sp_runtime_interface/attr.runtime_interface.html
#[sp_runtime_interface::runtime_interface]
pub trait Fragnova {
	// these are called NATIVE from even WASM
	// that's the deal

	/// A function that can be called from native/wasm.
	///
	/// The implementation given to this function is only compiled on native.
	fn say_hello_world(data: &str) {
		details::_say_hello_world(data);
	}

	/// TODO
	fn on_new_fragment(_fragment_hash: &Hash256) -> bool {
		log::debug!("sp_fragnova on_new_fragment called...");
		true
	}

	/// Get the URL of the Fragnova-owned Geth Node
	fn get_geth_url() -> Option<Vec<u8>> {
		details::_get_geth_url()
	}
}

/// Set the Fragnova-owned Geth Node's URL
#[cfg(feature = "std")]
pub fn init(geth_url: Option<String>) {
	if let Some(geth_url) = geth_url {
		*details::GETH_URL.lock().unwrap() = Some(geth_url.into_bytes());
	}

	// use chainblocks::{cbl_env, shlog};

	// details::init(fetch_extrinsic);

	// // needs to go first!
	// chainblocks::core::init();

	// shlog!("Chainblocks initializing...");

	// // load default chains
	// let chain = cbl_env!(include_str!("validate_fragment.edn"));

	// shlog!("Chainblocks initialized!");
}

/// Make an HTTP POST Request with data `body` to the URL `url`
pub fn http_json_post(
	url: &str,
	body: &[u8],
	wait: Option<Timestamp>,
) -> Result<Vec<u8>, &'static str> {
	log::debug!("sp_fragnova http_request called...");

	let request =
		offchain::http_request_start("POST", url, &[]).map_err(|_| "Failed to start request")?;

	offchain::http_request_add_header(request, "Content-Type", "application/json")
		.map_err(|_| "Failed to add header")?;

	offchain::http_request_write_body(request, body, None).map_err(|_| "Failed to write body")?;

	// send off the request
	offchain::http_request_write_body(request, &[], None).unwrap();

	let results = offchain::http_response_wait(&[request], wait);
	let status = results[0];

	match status {
		HttpRequestStatus::Finished(status) => match status {
			200 => {
				let mut response_body: Vec<u8> = Vec::new();
				loop {
					let mut buffer = Vec::new();
					buffer.resize(1024, 0);
					let len =
						offchain::http_response_read_body(request, &mut buffer, None).unwrap();
					if len == 0 {
						break
					}
					response_body.extend_from_slice(&buffer[..len as usize]);
				}
				Ok(response_body)
			},
			_ => {
				log::error!("request had unexpected status: {}", status);
				Err("request had unexpected status")
			},
		},
		HttpRequestStatus::DeadlineReached => {
			log::error!("request failed for reached timeout");
			Err("timeout reached")
		},
		_ => {
			log::error!("request failed with status: {:?}", status);
			Err("request failed")
		},
	}
}

/// Returns an account ID that can stake FRAG tokens.
/// This returned account ID is determinstically computed from the given account ID (`who`).
pub fn get_locked_frag_account<TAccountId: Encode + Decode>(
	who: &TAccountId,
) -> Result<TAccountId, CodecError> {
	// the idea is to use an account that users cannot access
	let mut who = who.encode();
	who.append(&mut b"frag-locked-account".to_vec());
	let who = blake2_256(&who);
	TAccountId::decode(&mut &who[..])
}

/// **Get** an **Account ID** deterministically computed from an input `hash`**.
pub fn get_vault_id<TAccountId: Encode + Decode>(hash: Hash128) -> TAccountId {
	let hash = blake2_256(&[&b"fragnova-vault"[..], &hash].concat());
	TAccountId::decode(&mut &hash[..]).expect("T::AccountId should decode")
}