Le code qui va Ăªtre prĂ©sentĂ© correspond est le code python modifiĂ© du programme Blockshell afin qu’il puisse stocker dans sa blockchain, le profile d’un Ă©tudiant EPITA.
La première modification a été apportée dans le backend du programme. Une structure Data a été créée afin de transporter les données relatives à un étudiant EPITA:
# ==================================================
# =================== DATA CLASS ==================
# ==================================================
# Creation d'un modele specifique de donnĂ©e qui pourra Ăªtre contenu par nos blocks
class Data:
def __init__(self, uid, email, firstname, secondname, image):
self.uid = uid
self.email = email
self.firstname = firstname
self.secondname = secondname
self.image = image
# implementation obligatoire pour que Block puisse créer le hash à partir d'un object de classe Data
def __str__(self):
return str(self.uid) + str(self.email) + str(self.firstname) + str(self.secondname) + str(self.image)
Cette nouvelle structure a Ă©tĂ© créée dans le but de donner de la gĂ©nĂ©ricitĂ© au programme puisque la classe Block possède un attribut data dont le type peut varier. La seule condition implicite sur l’attribut data apparaĂ®t lors du calcul du hash du Block. L’attribut data doit pouvoir Ăªtre converti en string. C’est pour cette raison que la mĂ©thode __str__() a Ă©tĂ© implĂ©mentĂ© dans la classe Data.
# ==================================================
# =================== BLOCK CLASS ==================
# ==================================================
class Block:
"""
Create a new block in chain with metadata
"""
def __init__(self, data, index=0):
self.index = index
self.previousHash = ""
self.data = data
self.timestamp = str(datetime.datetime.now())
self.nonce = 0
self.hash = self.calculateHash()
def calculateHash(self):
"""
Method to calculate hash from metadata
"""
hashData = str(self.index) + str(self.data) + self.timestamp + self.previousHash + str(self.nonce)
return hashlib.sha256(hashData).hexdigest()
La sĂ©rialisation dans le fichier chain.txt doit donc Ăªtre ajustĂ© Ă la gĂ©nĂ©ricitĂ© de la structure d’un bloc. Pour cela, on va s’appuyer sur la fonction getDict() qui va convertir rĂ©cursivement un objet en imbrication de dictionnaires contenant les attributs de chaque objet.
# Créer un dictionnaire récursivement à partir d'un objet
def getDict(obj):
data = {}
for key,value in obj.__dict__.iteritems():
try:
data[key] = getDict(value)
except AttributeError:
# si l'object n'a pas de méthode __dict__
data[key] = value
return data
# ==================================================
# ================ BLOCKCHAIN CLASS ================
# ==================================================
class Blockchain:
# [...]
def writeBlocks(self):
"""
Method to write new mined block to blockchain
"""
dataFile = file("chain.txt", "w")
chainData = []
for eachBlock in self.chain:
# Pour écrire les blocks, il va falloir les lire de manière récursive du fait de notre
# implementation de Data, contenue dans les blocks
chainData.append(getDict(eachBlock))
dataFile.write(json.dumps(chainData, indent=4))
dataFile.close()
Maintenant que la structure des blocks a Ă©tĂ© adaptĂ©e, il faut que l’utilisateur puisse interagir avec le programme.
Dans un premier temps, il faut une fonction capable de passer les paramètres qui seront passés aux commandes. Cette fonction prend un dictionnaire en entrée. Chaque clé du dictionnaire correspond à un flag, et chaque valeur à sa valeur par défaut. La fonction modifie le dictionnaire par référence, et return le nombre de flag récupérés dans la commande.
# fonction qui permet d'associer les flags avec leur valeur
# txData: str -> la chaine de caractère brute sans le nom de la commande
## --uid uid_value --image image_value
# options: dict -> contient tout les flags Ă intercepter
## {'uid':'', 'image':''}
def parseArgs(txData, options):
# Decoupe de la commande sur les balises de flags
cmd = txData.split(' --')
# On fait une copie de l'état des flags afin de déterminer à la fin de la fonction
# Combien de paramètres on été renseignés
options_copy = options.copy()
# pour chaque string contenant le flag suivit de sa valeur séparés par un espace
for i in range(len(cmd)):
# on enlève les balises de flag restantes (il ne devrait plus y en avoir)
cmd[i] = cmd[i].replace("--", '')
# on sépare le flag de sa valeur
cmd[i] = cmd[i].split(' ')
# pour chaque couple [flag, value]
for couple in cmd:
if len(couple) != 2:
print("[UNKNOWN SYNTAXE]: " + str(couple))
continue
# si le flag est intercepté par options
if couple[0] in options:
# on complete le champ flag du dict par sa valeur recupérée
options[couple[0]] = couple[1]
else:
# sinon on affiche à l'utilisateur la non considération du flag
print("[IGNORED]: " + couple[0] + " " + couple[1])
return flagsGivenCount(options, options_copy)
Une fois le parseur créé, lees nouvelles commandes peuvent Ăªtre ajoutĂ©es
Pour les besoins du sujet, une image en base64 sera générée par défaut:
flags:
def generateRandomImage():
array = numpy.random.rand(9,9,3) * 255
image = Image.fromarray(array.astype('uint8')).convert('RGB')
image_file = BytesIO()
image.save(image_file, format='JPEG')
image_bytes = image_file.getvalue()
b_64 = base64.b64encode(image_bytes)
return b_64
Il faut tout d’abord un commande qui puisse interpreter notre nouvelle structure de bloc, afin de les ajouter Ă la blockchain.
# Créé un nouveau block avec le pattern d'un profil EPITA
def newblock(cmd):
"""
Do Transaction - Method to perform new transaction on blockchain.
"""
if (cmd.strip() == "newblock"):
txData = ''
else:
txData = cmd.split("newblock ")[-1]
# Génération d'un image aléatoire
image_b64 = generateRandomImage()
# options regroupe tous les flags que la commande s'attend Ă retrouver, et uniquement ceux-ci
options = {"uid":'', "email":'', "firstname":'', "secondname":'', "image":image_b64}
if "{" in txData:
txData = json.loads(txData)
else:
args = parseArgs(txData, options)
print "Doing transaction..."
# On créé un nouvel objet Data avec le dictionnaire des flags (passage générique des paramètres par pointeur)
d = Data(**options)
# On créé le block à partir de l'objet Data généré et on l'ajoute à la blockchain coin
coin.addBlock(Block(data=d))
Il faut ensuite commande qui permette d’Ă©changer 2 noms. Celle-ci devra dans un premier temps vĂ©rifier la validitĂ© des champs renseignĂ©s par l’utilisateur, en s’assurant que tous les flags on Ă©tĂ© renseignĂ©s.
flags:
def swapnames(cmd):
if (cmd.strip() == "swapnames"):
txData = ''
else:
txData = cmd.split("swapnames ")[-1]
# options regroupe tous les flags que la commande s'attend Ă retrouver, et uniquement ceux-ci
options = {"index1":'', "index2":''}
# On vérifie que le nombre de paramètres renseigné est suffisant pour permettre le swap
count_args = parseArgs(txData, options)
if count_args != len(options):
print("missing param for swapnames: " + str(options))
return 1
# Appel Ă la fonction par dictionnaire
coin.swapNames(**options)
print "Doing transaction..."
La fonction swapnames() solicite le backend pour effectuer le changement. Celle-ci va confirmer la validitĂ© du changement en s’assurant que chacun des blocks identifiĂ©s dans l’interversion possède effectivement un attribut secondname. Si tel est le cas, les deux blocs sont modifiĂ©s:
Attributs modifiés sur les deux blocs:
Attributs modifiés pour tous les blocs à partir du min(index1, index2):
Puis les blocks modifiés sont re-minés.
class Blockchain:
# [...]
def swapNames(self, index1, index2):
# L'attribut que l'on va swap dans les blocks
attribute = "secondname"
intIndex1 = int(index1)
intIndex2 = int(index2)
sizeBlockchain = len(self.chain)
# Rien a faire pour ce cas, la blockchain est dans son état final
if index1 == index2:
return 0
# Filtre de validation des valeurs d'entrée
if intIndex1 <= 0 or intIndex1 >= sizeBlockchain:
print("unvalid index1: " + index1 + " it should be in ]0, " + str(sizeBlockchain) + "[")
return 1
if intIndex2 <= 0 or intIndex2 >= sizeBlockchain:
print("unvalid index2: " + index2 + " it should be in ]0, " + str(sizeBlockchain) + "[")
return 1
# Récupération des blocks à swap et de leur position dans la blockchain
metaDataBlock1 = self.findBlockWithIndex(intIndex1)
metaDataBlock2 = self.findBlockWithIndex(intIndex2)
# On vérifie que les blocks on bien été trouvés
if metaDataBlock1 is None:
print("block at " + index1 + " not found in the blockchain")
return 1
if metaDataBlock2 is None:
print("block at " + index2 + " not found in the blockchain")
return 1
# Extraction des données récupérées
pos1, block1 = metaDataBlock1
pos2, block2 = metaDataBlock2
# La généricité de l'attribut data de la classe Block fait que nous sommes obligés de vérifier
# l'existance de l'attribut data.attribute (ici data.secondname)
# de l'attribut
if attribute not in block1.data.__dict__:
print(attribute + " not found in index1: " + index1 + "\n" + str(block1))
return 1
if attribute not in block2.data.__dict__:
print(attribute + " not found in index2: " + index2 + "\n" + str(block2))
return 1
# Swap des names
tmpAttribute = self.chain[pos1].data.__dict__[attribute]
block1.data.__dict__[attribute] = block2.data.__dict__[attribute]
block2.data.__dict__[attribute] = tmpAttribute
# Modification de toute la blockchain à partir du premier block modifié
# Tous les blocks positionnées en amont du premier block modifié ne changent pas
for currentPos in range(min(pos1, pos2), len(self.chain)):
currentBlock = self.chain[currentPos]
previousPos = currentPos - 1
currentBlock.previousHash = self.chain[previousPos].hash
currentBlock.timestamp = str(datetime.datetime.now())
currentBlock.hash = currentBlock.calculateHash()
currentBlock.mineBlock(self.difficulty)
# Réécriture du fichier chain.txt avec la chaine éditée
self.writeBlocks()
return 0
Il est Ă prĂ©sent possible d’intervertir les noms de 2 blocs, tout en conservant les moyens de vĂ©rification de l’intĂ©gritĂ©.
Afin de faciliter l’Ă©criture de scripts, il est possible de crĂ©er une commande qui permette d’embarquer plusieurs autres commandes.
Celle-ci va se contenter de parcourir l’ensemble des commandes sĂ©parĂ©es par un ‘;’ et les exĂ©cuter.
# Command qui permet de lancer plusieurs autres commandes sur une mĂªme ligne
# Utile pour le script shell
def commandlist(cmd):
if (cmd.strip() == "commandlist"):
txData = ''
else:
txData = cmd.split("commandlist ")[-1]
# Chaque commande est exécutée de manière séquentielle
for command in txData.split(';'):
command = command.strip()
processInput(command.strip())
Récupérer les références vers les fichiers .css et .js.
Entrer l’url dans le navigateur (1):
https://getbootstrap.com/docs/4.0/getting-started/introduction/
Copier la balise link faisant référence à un fichier .css (2)
Copier les baslises script faisant référence à des fichiers .js (3):

Editer les fichiers:
de la façon suivante:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<!-- Custom styles for this template -->
<!-- ... -->
</head>
<body>
<!-- ... -->
<!-- Bootstrap core JavaScript -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>