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
use crate::configuration::get_configuration;
use crate::crypto::SaitoHash;
use crate::golden_ticket::GoldenTicket;
use crate::miner::Miner;
use crate::network::Network;
use crate::storage::Storage;
use crate::test_utilities::test_manager::TestManager;
use crate::wallet::Wallet;
use crate::{blockchain::Blockchain, mempool::Mempool, transaction::Transaction};
use clap::{App, Arg};
use std::sync::Arc;
use tokio::signal;
use tokio::sync::RwLock;
use tokio::sync::{broadcast, mpsc};

///
/// Saito has the following system-wide messages which may be sent and received
/// over the main broadcast channel. Convention has the message begin with the
/// class that is broadcasting.
///
#[derive(Clone, Debug)]
pub enum SaitoMessage {
    // broadcast when a block is received but parent is unknown
    MissingBlock { peer_id: SaitoHash, hash: SaitoHash },
    // broadcast when the longest chain block changes
    BlockchainNewLongestChainBlock { hash: SaitoHash, difficulty: u64 },
    // broadcast when a block is successfully added
    BlockchainAddBlockSuccess { hash: SaitoHash },
    // broadcast when a block is unsuccessful at being added
    BlockchainAddBlockFailure { hash: SaitoHash },
    // broadcast when the miner finds a golden ticket
    MinerNewGoldenTicket { ticket: GoldenTicket },
    // broadcast when the blockchain wants to broadcast a block to peers
    BlockchainSavedBlock { hash: SaitoHash },
    // handle transactions which we've created "ourself" - interact with saitocli
    WalletNewTransaction { transaction: Transaction },
}

///
/// The entry point to the Saito consensus runtime
///
pub async fn run() -> crate::Result<()> {
    //
    // handle shutdown messages w/ broadcast channel
    //
    let (notify_shutdown, _) = broadcast::channel(1);
    let (shutdown_complete_tx, shutdown_complete_rx) = mpsc::channel(1);
    let mut consensus = Consensus {
        _notify_shutdown: notify_shutdown,
        _shutdown_complete_tx: shutdown_complete_tx,
        _shutdown_complete_rx: shutdown_complete_rx,
    };

    //
    // initiate runtime and handle results
    //
    tokio::select! {
        res = consensus.run() => {
            if let Err(err) = res {
                eprintln!("{:?}", err);
            }
        },
        _ = signal::ctrl_c() => {
            println!("Shutting down!")
        }
    }

    Ok(())
}

//
// The consensus state exposes a run method that main
// calls to initialize Saito state and prepare for
// shutdown.
//
struct Consensus {
    _notify_shutdown: broadcast::Sender<()>,
    _shutdown_complete_rx: mpsc::Receiver<()>,
    _shutdown_complete_tx: mpsc::Sender<()>,
}

impl Consensus {
    //
    // Run consensus
    //
    async fn run(&mut self) -> crate::Result<()> {
        //
        // create main broadcast channel
        //
        // all major classes have send/receive access to the main broadcast
        // channel, and can communicate by sending the events listed in the
        // SaitoMessage list above.
        //
        let (broadcast_channel_sender, broadcast_channel_receiver) = broadcast::channel(32);

        //
        // handle command-line arguments
        //
        let matches = App::new("Saito Runtime")
            .about("Runs a Saito Node")
            .arg(
                Arg::with_name("wallet")
                    .short("w")
                    .long("wallet")
                    .default_value("none")
                    .takes_value(true)
                    .help("Path to local wallet"),
            )
            .arg(
                Arg::with_name("password")
                    .short("p")
                    .long("password")
                    .default_value("password")
                    .takes_value(true)
                    .help("Password to decrypt wallet"),
            )
            .arg(
                Arg::with_name("spammer")
                    .short("s")
                    .long("spammer")
                    .help("enable tx spamming"),
            )
            .get_matches();

        //TODO: spammer just served for testing app
        // - should be in another bin crate instead of a adhoc flag
        let mut is_spammer_enabled = false;
        //
        // hook up with Arg above
        //
        if matches.is_present("spammer") {
            is_spammer_enabled = true;
        };

        // Load configurations based on env
        let settings = get_configuration().expect("Failed to read configuration.");

        //
        // generate core system components
        //
        // the code below creates an initializes our core system components:
        //
        //  - wallet
        //  - blockchain
        //  - mempool
        //  - miner
        //  - network
        //
        let wallet_lock = Arc::new(RwLock::new(Wallet::new()));

        //
        // if a wallet and password are provided Saito will attempt to load
        // it from the /data/wallets directory. If they are not we will create
        // a new wallet and save it as "default" with the password "password".
        // this "default" wallet will be over-written every time the software
        // starts, but can be renamed afterwards if need be since it will
        // persist until the software is restarted.
        //
        {
            let walletname = matches.value_of("wallet").unwrap();
            let password = matches.value_of("password").unwrap();

            if walletname != "none" {
                let mut wallet = wallet_lock.write().await;
                wallet.set_filename(walletname.to_string());
                wallet.set_password(password.to_string());
                wallet.load();
            } else {
                let mut wallet = wallet_lock.write().await;
                wallet.save();
            }
        }

        let blockchain_lock = Arc::new(RwLock::new(Blockchain::new(wallet_lock.clone())));

        //
        // load blocks from disk and check chain
        //
        Storage::load_blocks_from_disk(blockchain_lock.clone()).await;

        //
        // instantiate core classes
        //
        // all major classes which require multithread read / write access are
        // wrapped in Tokio::RwLock for read().await / write().await access.
        // we will send a clone of this RwLock object in any object that will
        // require direct access when initializing the object below.
        //
        let mempool_lock = Arc::new(RwLock::new(Mempool::new(wallet_lock.clone())));
        let miner_lock = Arc::new(RwLock::new(Miner::new(wallet_lock.clone())));
        let network_lock = Arc::new(RwLock::new(Network::new(
            settings,
            blockchain_lock.clone(),
            mempool_lock.clone(),
            wallet_lock.clone(),
            broadcast_channel_sender.clone(),
        )));

        //
        // the configuration file should be used to update the network so that
        // the server and peers can be loaded correctly.
        //
        /********
                {
                    let walletname = matches.value_of("wallet").unwrap();
                    let password = matches.value_of("password").unwrap();

                    if walletname != "none" {
                        let mut wallet = wallet_lock.write().await;
                        wallet.set_filename(walletname.to_string());
                        wallet.set_password(password.to_string());
                        wallet.load();
                    } else {
                        let mut wallet = wallet_lock.write().await;
                        wallet.save();
                    }
                }
        ********/

        //
        // start test_manager spammer
        //
        if is_spammer_enabled {
            let mut test_manager = TestManager::new(blockchain_lock.clone(), wallet_lock.clone());
            test_manager.spam_mempool(mempool_lock.clone());
        }

        //
        // initialize core classes.
        //
        // all major classes get a clone of the broadcast channel sender and
        // broadcast channel receiver. They must receive this clone and assign
        // it to a local object so they have read/write access to cross-system
        // messages.
        //
        // The SaitoMessage ENUM above contains a list of all cross-
        // system notifications.
        //
        tokio::select! {

        //
        // Mempool
        //
            res = crate::mempool::run(
                mempool_lock.clone(),
                blockchain_lock.clone(),
                broadcast_channel_sender.clone(),
                broadcast_channel_receiver,
            ) => {
                if let Err(err) = res {
                    eprintln!("mempool err {:?}", err)
                }
            },

        //
        // Blockchain
        //
            res = crate::blockchain::run(
                blockchain_lock.clone(),
                broadcast_channel_sender.clone(),
                broadcast_channel_sender.subscribe()
            ) => {
                if let Err(err) = res {
                    eprintln!("blockchain err {:?}", err)
                }
            },

        //
        // Miner
        //
            res = crate::miner::run(
                miner_lock.clone(),
                broadcast_channel_sender.clone(),
                broadcast_channel_sender.subscribe()
            ) => {
                if let Err(err) = res {
                    eprintln!("miner err {:?}", err)
                }
            },

        //
        // Network
        //
            res = crate::network::run(
                network_lock.clone(),
                broadcast_channel_sender.clone(),
                broadcast_channel_sender.subscribe()
            ) => {
                if let Err(err) = res {
                    eprintln!("miner err {:?}", err)
                }
            },
        //
        // Other
        //
            _ = self._shutdown_complete_tx.closed() => {
                println!("Shutdown message complete")
            }
        }

        Ok(())
    }
}