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
/*
* Copyright (C) 2020  Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

//! Ledger data-structure for the blockchain
//!
//! ## Create a new blockchain
//! ```rust
//! use damn_vuln_blockchain::{ asset::AssetLedger, block::{BlockBuilder, Block}, chain::Chain};
//!
//! fn main() {
//!        let chain = Chain::new("My chain"); // crate cahin
//!   }
//! ```
//!
//! The easiest way to interact with the ledger is via the [Chain] actor.
//!
//! # [Chain] supports the followings messages:
//! - [AddBlock]: adds a [Block] to the blockchain
//! - [GetLastBlock]: get's the latest [Block] in the blockchain
//! - [DumpLedger]: dumps the entire ledger
//! - [ReplaceChain]: replaces a [Vec<Block>] inside the [Chain] data-structure, useful
//! when synchronising ledgers

use actix::prelude::*;
use serde::{Deserialize, Serialize};

use crate::block::Block;
use crate::error::*;

/// Ledger data-structure for the blockchain
///
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Chain {
    name: String,
    blocks: Vec<Block>,
}

/// # [Chain] supports the followings messages:
/// - [AddBlock]: adds a [Block] to the blockchain
/// - [GetLastBlock]: get's the latest [Block] in the blockchain
/// - [DumpLedger]: dumps the entire ledger
/// - [ReplaceChain]: replaces a [Vec<Block>] inside the [Chain] data-structure, useful
/// when synchronising ledgers

impl Chain {
    /// create new blockchain
    pub fn new(name: &str) -> Chain {
        let genesis = Block::genesis();
        let blocks = vec![genesis];
        Chain {
            name: name.into(),
            blocks,
        }
    }

    /// get the last block in the chain
    pub fn get_last_block(&self) -> &Block {
        // unwrap is okay here because chain initiation guarentees
        // block creation. Sot self.blocks.last() will always
        // return Some(Block)
        self.blocks.last().unwrap()
    }

    /// add a block to the chain
    /// ChainError::GenesisBlockAdditionError error is returned when
    /// a genesis block is passed. Genesis blocks are only valid when
    /// a chain is created.
    pub fn add_block(&mut self, mut block: Block, network_size: usize) -> ChainResult<usize> {
        if block.is_genesis() {
            return Err(ChainError::GenesisBlockAdditionError);
        // unwrap() is fine below because `block` is not genesis
        } else if block.get_prev().unwrap() != self.get_last_block().get_hash() {
            return Err(ChainError::InconsistentBlockAdition);
        } else {
            // unwrap is okay here because [Block::genesis()] starts with
            // serial_no = 1 and every other block that gets added to the chain
            // will have its serial number set
            let last_serial_no = self.get_last_block().get_serial_no().unwrap();
            let serial_no = if last_serial_no == 0 {
                network_size + 1
            } else {
                last_serial_no + 1
            };

            block.set_serial_no(serial_no);
            self.blocks.push(block);
            return Ok(serial_no);
        }
    }

    /// checks if a blockchain is valid by comparing the hash of the previous
    /// element with the block.prev of the next element in the blockchain
    pub fn is_valid(chain: &Vec<Block>) -> ChainResult<()> {
        let mut iter = chain.iter().peekable();
        loop {
            if let Some(val) = iter.next() {
                if let Some(next) = iter.peek() {
                    if &val.hash() != next.get_prev().unwrap() {
                        //unwrap is okay
                        // here as we'll only be passing non-genesis blocks
                        return Err(ChainError::InvalidBlockChain);
                    }
                }
            } else {
                break;
            }
        }
        Ok(())
    }

    pub fn replace_chain(&mut self, chain: Vec<Block>) -> ChainResult<()> {
        Chain::is_valid(&chain)?;
        self.blocks = chain;
        Ok(())
    }
}

impl Actor for Chain {
    type Context = Context<Self>;
}

/// Add Block
/// send block and network_size
/// network_size is required becuase when InitNetwork is called
/// it sets an offset for Block.serial_no = network_size
#[derive(Message)]
#[rtype(result = "ChainResult<usize>")]
pub struct AddBlock(pub Block, pub usize);

/// Get last block
#[derive(Message)]
#[rtype(result = "Block")]
pub struct GetLastBlock;

/// Replace Chain
#[derive(Message)]
#[rtype(result = "ChainResult<()>")]
pub struct ReplaceChain(pub Vec<Block>);

/// Dumps entire ledger
/// Useful when forking:
/// send `DumpLedger` and send output with `ReplaceChain`
#[derive(Message)]
#[rtype(result = "Vec<Block>")]
pub struct DumpLedger;

impl Handler<AddBlock> for Chain {
    type Result = MessageResult<AddBlock>;

    fn handle(&mut self, msg: AddBlock, _ctx: &mut Self::Context) -> Self::Result {
        MessageResult(self.add_block(msg.0, msg.1))
    }
}

impl Handler<GetLastBlock> for Chain {
    type Result = MessageResult<GetLastBlock>;

    fn handle(&mut self, _msg: GetLastBlock, _ctx: &mut Self::Context) -> Self::Result {
        MessageResult(self.get_last_block().to_owned())
    }
}

impl Handler<ReplaceChain> for Chain {
    type Result = MessageResult<ReplaceChain>;

    fn handle(&mut self, msg: ReplaceChain, _ctx: &mut Self::Context) -> Self::Result {
        MessageResult(self.replace_chain(msg.0))
    }
}

impl Handler<DumpLedger> for Chain {
    type Result = MessageResult<DumpLedger>;

    fn handle(&mut self, _msg: DumpLedger, _ctx: &mut Self::Context) -> Self::Result {
        MessageResult(self.blocks.clone())
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use crate::block::*;

    #[test]
    fn chain_works() {
        use crate::asset::AssetLedger;

        let mut chain = Chain::new("test chain");
        assert_eq!(
            chain.get_last_block().get_serial_no().unwrap(),
            0,
            "genesis serial number properly set"
        );

        let prev = chain.get_last_block();

        let mut assets = AssetLedger::generate("Me");
        let asset = assets.assets.pop().unwrap();

        let block = BlockBuilder::default()
            .set_tx("Me")
            .set_rx("You")
            .set_prev(&prev)
            .set_asset_id(&asset.get_hash())
            .build();
        let network_size = 3;

        assert_eq!(
            chain.add_block(Block::genesis(), network_size),
            Err(ChainError::GenesisBlockAdditionError),
            "Genesis Block addition prevented"
        );

        chain.add_block(block.clone(), network_size).unwrap();
        assert_eq!(
            chain.get_last_block().hash(),
            block.hash(),
            "add_block works"
        );

        assert_eq!(
            chain.get_last_block().get_serial_no().unwrap(),
            4,
            "serial number properly set"
        );

        assert_eq!(
            chain.add_block(block, network_size),
            Err(ChainError::InconsistentBlockAdition),
            "Chain Invalid Prevention works"
        );
    }

    #[actix_rt::test]
    async fn chain_actor_works() {
        use crate::asset::AssetLedger;

        let chain_addr = Chain::new("test chain").start();

        let prev = chain_addr.send(GetLastBlock).await.unwrap();

        let mut assets = AssetLedger::generate("Me");
        let asset = assets.assets.pop().unwrap();

        let block = BlockBuilder::default()
            .set_tx("Me")
            .set_rx("You")
            .set_prev(&prev)
            .set_asset_id(&asset.get_hash())
            .build();

        let network_size = 3;

        // checks if genesis block can be appended to a blockchian
        assert_eq!(
            chain_addr
                .send(AddBlock(Block::genesis(), network_size))
                .await
                .unwrap(),
            Err(ChainError::GenesisBlockAdditionError),
            "Genesis Block addition prevented"
        );

        // checks if valid blocks can be added to blockchian
        chain_addr
            .send(AddBlock(block.clone(), network_size))
            .await
            .unwrap()
            .unwrap();
        assert_eq!(
            chain_addr.send(GetLastBlock).await.unwrap().hash(),
            block.hash(),
            "add_block works"
        );

        // checks if invalid block, where block.get_prev() != chain.get_last_block().get_hash()
        // can be added to chain
        assert_eq!(
            chain_addr
                .send(AddBlock(block.clone(), network_size))
                .await
                .unwrap(),
            Err(ChainError::InconsistentBlockAdition),
            "Chain Invalid Prevention works"
        );
        let dump = chain_addr.send(DumpLedger).await.unwrap().pop().unwrap();

        // checks if dump works by popping the last element of the dump and getting
        // its hash and comparing it with the chain's last element's hash
        assert_eq!(
            chain_addr.send(GetLastBlock).await.unwrap().hash(),
            dump.get_hash(),
            "Dump works"
        );
    }

    #[actix_rt::test]
    async fn chain_replace_works() {
        use crate::asset::AssetLedger;

        // create parallel_chain to get genesis hash
        let parallel_chain = Chain::new("test chain");

        let prev = parallel_chain.get_last_block();

        let mut assets = AssetLedger::generate("Me");
        let asset = assets.assets.pop().unwrap();

        let block = BlockBuilder::default()
            .set_tx("Me")
            .set_rx("You")
            .set_prev(&prev)
            .set_asset_id(&asset.get_hash())
            .build();

        //        let main_last_block_hash = chain_addr.send(GetLastBlock).await.unwrap().get_hash();
        // get parallel_chain's hash
        let parallel_last_block_hash = block.get_hash();

        // create invalid block chain
        let chain_invalid = vec![block.clone(), block.clone()];
        assert_eq!(
            Chain::is_valid(&chain_invalid),
            Err(ChainError::InvalidBlockChain),
            "Invalid Blockchain test"
        );

        // create valid blockchain
        let parallel_chain_valid = vec![prev.clone(), block.clone()];

        // create chain that needs to be replaced
        let chain_addr = Chain::new("test chain").start();

        // get previous block to add new block
        let prev = chain_addr.send(GetLastBlock).await.unwrap();

        let mut assets = AssetLedger::generate("Me");
        let asset = assets.assets.pop().unwrap();

        let new_block = BlockBuilder::default()
            .set_tx("Me")
            .set_rx("You")
            .set_prev(&prev)
            .set_asset_id(&asset.get_hash())
            .build();

        let network_size = 3;

        // add new block
        chain_addr
            .send(AddBlock(new_block.clone(), network_size))
            .await
            .unwrap()
            .unwrap();

        // attempt replace
        chain_addr
            .send(ReplaceChain(parallel_chain_valid))
            .await
            .unwrap()
            .unwrap();

        // check if it's been replaced
        assert_eq!(
            chain_addr.send(GetLastBlock).await.unwrap().get_hash(),
            parallel_last_block_hash,
            "Chain Replaced"
        );

        // attempt replace with invalid blockchain
        assert_eq!(
            chain_addr.send(ReplaceChain(chain_invalid)).await.unwrap(),
            Err(ChainError::InvalidBlockChain),
            "Invalild blockchain replace test"
        );
    }
}