Solved hash generation
This commit is contained in:
@@ -2,40 +2,37 @@ package com.rviewer.skeletons.domain.model;
|
|||||||
|
|
||||||
import com.rviewer.skeletons.domain.exception.HashGenerationException;
|
import com.rviewer.skeletons.domain.exception.HashGenerationException;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.With;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@With
|
@Builder
|
||||||
public class Block {
|
public class Block {
|
||||||
private LocalDateTime timestamp;
|
@Builder.Default
|
||||||
private String lasthash;
|
private LocalDateTime timestamp = LocalDateTime.now();
|
||||||
|
private String previousHash;
|
||||||
private String data;
|
private String data;
|
||||||
private Integer nonce;
|
@Builder.Default
|
||||||
|
private Integer nonce = RandomNonce.getRandomNonce();
|
||||||
private String hash;
|
private String hash;
|
||||||
|
|
||||||
public Block() {
|
|
||||||
this.timestamp = LocalDateTime.now();
|
|
||||||
this.nonce = new Random().nextInt();
|
|
||||||
this.hash = generateHashFromBlock(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Block getGenesisBlock() {
|
public static Block getGenesisBlock() {
|
||||||
return new Block().withLasthash("");
|
Block genesisBlock = Block.builder().previousHash("").build();
|
||||||
|
genesisBlock.setHash(generateHashFromBlock(genesisBlock));
|
||||||
|
return genesisBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String generateHashFromBlock(final Block block) throws HashGenerationException {
|
public static String generateHashFromBlock(final Block block) throws HashGenerationException {
|
||||||
try {
|
try {
|
||||||
final var digest = MessageDigest.getInstance("SHA-256");
|
final var digest = MessageDigest.getInstance("SHA-256");
|
||||||
final byte[] hash = digest.digest(block.getHashData().getBytes(StandardCharsets.UTF_8));
|
final byte[] hashData = digest.digest(block.getHashData().getBytes(StandardCharsets.UTF_8));
|
||||||
final var hexString = new StringBuilder();
|
final var hexString = new StringBuilder();
|
||||||
for (final byte elem : hash) {
|
for (final byte elem : hashData) {
|
||||||
final var hex = Integer.toHexString(0xff & elem);
|
final var hex = Integer.toHexString(0xff & elem);
|
||||||
if (hex.length() == 1) hexString.append('0');
|
if (hex.length() == 1) hexString.append('0');
|
||||||
hexString.append(hex);
|
hexString.append(hex);
|
||||||
@@ -46,7 +43,11 @@ public class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void generateHash() {
|
||||||
|
hash = generateHashFromBlock(this);
|
||||||
|
}
|
||||||
|
|
||||||
private String getHashData() {
|
private String getHashData() {
|
||||||
return timestamp + lasthash + data + nonce;
|
return timestamp + previousHash + data + nonce;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,35 @@ public class Blockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean replace(List<Block> blockchainList) {
|
public Boolean replace(List<Block> blockchainList) {
|
||||||
return true;
|
if (blockchainList.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (blockchainList.size() < this.blockchainList.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<Block> newBlockchainList = blockchainList.subList(0, this.blockchainList.size());
|
||||||
|
if (!newBlockchainList.equals(this.blockchainList)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isValid(blockchainList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isValid(List<Block> blockchainList) {
|
public Boolean isValid(List<Block> blockchainList) {
|
||||||
return true;
|
boolean valid = true;
|
||||||
|
// Validate genesis block
|
||||||
|
if (!blockchainList.get(0).getPreviousHash().equals("")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Validate every element's last hash value matches previous block's hash
|
||||||
|
for (int i = 0; i < blockchainList.size() - 1; i++) {
|
||||||
|
valid = blockchainList.get(i).getHash().equals(blockchainList.get(i + 1).getPreviousHash());
|
||||||
|
if (!valid) return false;
|
||||||
|
}
|
||||||
|
// Validate data hasn't been tampered
|
||||||
|
for (Block block : blockchainList) {
|
||||||
|
valid = block.getHash().equals(Block.generateHashFromBlock(block));
|
||||||
|
if (!valid) break;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.rviewer.skeletons.domain.model;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class RandomNonce {
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
private RandomNonce() {}
|
||||||
|
|
||||||
|
public static Integer getRandomNonce() {
|
||||||
|
return RANDOM.nextInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,17 +30,21 @@ public class CartService implements CartServicePort {
|
|||||||
public void save(Cart cart) {
|
public void save(Cart cart) {
|
||||||
cartDatabasePort.save(cart);
|
cartDatabasePort.save(cart);
|
||||||
if (blockchain.getBlockchainList().isEmpty()) {
|
if (blockchain.getBlockchainList().isEmpty()) {
|
||||||
blockchain.addBlock(Block.getGenesisBlock().withData(cart.toString()));
|
blockchain.addBlock(Block.builder().data(cart.toString()).build());
|
||||||
} else {
|
} else {
|
||||||
blockchain.addBlock(new Block().withData(cart.toString())
|
Block saveBlock = Block.builder().data(cart.toString())
|
||||||
.withLasthash(blockchain.getBlockchainList().getLast().getHash()));
|
.previousHash(blockchain.getBlockchainList().getLast().getHash()).build();
|
||||||
|
saveBlock.generateHash();
|
||||||
|
blockchain.addBlock(saveBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
cartDatabasePort.delete(id);
|
cartDatabasePort.delete(id);
|
||||||
blockchain.addBlock(new Block().withData("deleted cart with id:" + id)
|
Block deleteBlock = Block.builder().data("deleted cart with id:" + id)
|
||||||
.withLasthash(blockchain.getBlockchainList().getLast().getHash()));
|
.previousHash(blockchain.getBlockchainList().getLast().getHash()).build();
|
||||||
|
deleteBlock.generateHash();
|
||||||
|
blockchain.addBlock(deleteBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
class CartControllerTest {
|
class CartControllerTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
@MockBean
|
@MockBean
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.rviewer.skeletons.domain.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class BlockchainTest {
|
||||||
|
private Blockchain blockchain;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() {
|
||||||
|
blockchain = new Blockchain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_add_block() {
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
assertEquals(blockchain.getBlockchainList().get(0), genesisBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_replace_blockchainList() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
newBlockchainList.add(secondBlock);
|
||||||
|
|
||||||
|
Block thirdBlock = Block.builder().previousHash(secondBlock.getHash()).data(null).build();
|
||||||
|
thirdBlock.generateHash();
|
||||||
|
newBlockchainList.add(thirdBlock);
|
||||||
|
|
||||||
|
assertTrue(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_is_empty() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_smaller() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_not_equals() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
newBlockchainList.add(Block.builder().previousHash(genesisBlock.getHash()).data("data").build());
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_invalid_genesis() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.builder().previousHash("non empty").data(null).build();
|
||||||
|
genesisBlock.generateHash();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
newBlockchainList.add(secondBlock);
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_hash_mismatch() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash("wrong hash").data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
newBlockchainList.add(secondBlock);
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_replace_blockchainList_because_tampered_data() {
|
||||||
|
List<Block> newBlockchainList = new ArrayList<>();
|
||||||
|
|
||||||
|
Block genesisBlock = Block.getGenesisBlock();
|
||||||
|
blockchain.addBlock(genesisBlock);
|
||||||
|
newBlockchainList.add(genesisBlock);
|
||||||
|
|
||||||
|
Block secondBlock = Block.builder().previousHash(genesisBlock.getHash()).data(null).build();
|
||||||
|
secondBlock.generateHash();
|
||||||
|
blockchain.addBlock(secondBlock);
|
||||||
|
secondBlock.setData("tampered data");
|
||||||
|
newBlockchainList.add(secondBlock);
|
||||||
|
|
||||||
|
assertFalse(blockchain.replace(newBlockchainList));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class CartDatabaseAdapterTest {
|
class CartDatabaseAdapterTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CartDatabaseAdapter cartDatabaseAdapter;
|
private CartDatabaseAdapter cartDatabaseAdapter;
|
||||||
@Mock
|
@Mock
|
||||||
|
|||||||
Reference in New Issue
Block a user