started working on next.py (next Gen iotaFS with async; smart caching; append-writing; ...)
This commit is contained in:
		
							parent
							
								
									16b1e502bd
								
							
						
					
					
						commit
						7aab26508d
					
				
							
								
								
									
										323
									
								
								next.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								next.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,323 @@
 | 
				
			|||||||
 | 
					from typing import Mapping, MutableMapping, Sequence, Iterable, List, Set
 | 
				
			||||||
 | 
					import pyfuse3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from iota import Iota, ProposedTransaction, Address, TryteString, Tag
 | 
				
			||||||
 | 
					from iota.crypto.addresses import AddressGenerator
 | 
				
			||||||
 | 
					from iota.crypto.types import Seed
 | 
				
			||||||
 | 
					from iota.codecs import TrytesDecodeError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from Crypto.Cipher import AES
 | 
				
			||||||
 | 
					from Crypto.Util.Padding import pad, unpad
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from pprint import pprint
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import msgpack
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import gzip
 | 
				
			||||||
 | 
					import secrets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CHUNKSIZE = 2187 - 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def log(txt):
 | 
				
			||||||
 | 
					    print("[-] "+str(txt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Atom():
 | 
				
			||||||
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        self.type = "null"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def load(self, data) -> None:
 | 
				
			||||||
 | 
					        if data[0]==True:
 | 
				
			||||||
 | 
					            self.type = "milestone"
 | 
				
			||||||
 | 
					            self.milestone = data[1:]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if data[1]==True:
 | 
				
			||||||
 | 
					                self.type = "dir"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.type = "file"
 | 
				
			||||||
 | 
					                self.size = data[5]
 | 
				
			||||||
 | 
					            self.name = data[2]
 | 
				
			||||||
 | 
					            self.token = data[3]
 | 
				
			||||||
 | 
					            self.milestoneIndex = data[4]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dump(self) -> bytes:
 | 
				
			||||||
 | 
					        # TODO: Delimiter?
 | 
				
			||||||
 | 
					        # TODO: compression?
 | 
				
			||||||
 | 
					        return msgpack.dump(self._dumpAsArray())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _dumpAsArray(self):
 | 
				
			||||||
 | 
					        if self.type=="milestone":
 | 
				
			||||||
 | 
					            return [True]+self.milestone
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return [False, self.type, self.name, self.token, self.milestoneIndex, self.size]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BlobChunk():
 | 
				
			||||||
 | 
					    def __init__(self, data: bytes = b'', sealed: bool = False) -> None:
 | 
				
			||||||
 | 
					        self.data = data
 | 
				
			||||||
 | 
					        self.sealded = sealed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getData(self) -> bytes:
 | 
				
			||||||
 | 
					        return self.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def append(self, data: bytes) -> None:
 | 
				
			||||||
 | 
					        if len(data)+len(self.data) > CHUNKSIZE:
 | 
				
			||||||
 | 
					            raise Exception("That to big!")
 | 
				
			||||||
 | 
					        self.data += data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getBytesLeft(self) -> int:
 | 
				
			||||||
 | 
					        if self.sealed:
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					        return CHUNKSIZE - len(self.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def seal(self) -> None:
 | 
				
			||||||
 | 
					        self.sealed = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def isSealed(self) -> bool:
 | 
				
			||||||
 | 
					        return self.sealed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TangleBlob():
 | 
				
			||||||
 | 
					    def __init__(self, token: bytes, iotaApi: Iota) -> None:
 | 
				
			||||||
 | 
					        self.token = token
 | 
				
			||||||
 | 
					        self.iotaApi = iotaApi
 | 
				
			||||||
 | 
					        self.preChunks = 0
 | 
				
			||||||
 | 
					        self.chunks = []
 | 
				
			||||||
 | 
					        m = hashlib.sha3_512()
 | 
				
			||||||
 | 
					        m.update(self.token)
 | 
				
			||||||
 | 
					        self.adressGen = AddressGenerator(Seed(m.digest()))
 | 
				
			||||||
 | 
					        self.fetched = False
 | 
				
			||||||
 | 
					        self.pushedNum = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _requireFetched(self):
 | 
				
			||||||
 | 
					        if not self.fetched:
 | 
				
			||||||
 | 
					            self.fetch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _getKey(self, chunkNum: int) -> bytes:
 | 
				
			||||||
 | 
					        m = hashlib.sha3_384()
 | 
				
			||||||
 | 
					        m.update(self.token)
 | 
				
			||||||
 | 
					        m.update(chunkNum.to_bytes(8, "little")) # 64 bits should be enought...
 | 
				
			||||||
 | 
					        m.update(self.token)
 | 
				
			||||||
 | 
					        return m.digest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _genBundle(self, data, addr) -> str:
 | 
				
			||||||
 | 
					        txMsg = TryteString.from_bytes(data)
 | 
				
			||||||
 | 
					        trans = ProposedTransaction(
 | 
				
			||||||
 | 
					            address = addr,
 | 
				
			||||||
 | 
					            value = 0,
 | 
				
			||||||
 | 
					            tag = Tag("IOTAFS"),
 | 
				
			||||||
 | 
					            message = txMsg
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        return self.iotaApi.prepare_transfer(
 | 
				
			||||||
 | 
					            transfers = [trans],
 | 
				
			||||||
 | 
					            inputs = [addr]
 | 
				
			||||||
 | 
					            )['trytes']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _dumpChunk(self, chunkNum: int, security_level=2) -> str:
 | 
				
			||||||
 | 
					        key = self._getKey(chunkNum + self.preChunks)
 | 
				
			||||||
 | 
					        data = self.chunks[chunkNum].getData()
 | 
				
			||||||
 | 
					        cipher = AES.new(key[16:][:16], AES.MODE_CBC, key[:16])
 | 
				
			||||||
 | 
					        ct_bytes = cipher.encrypt(pad(data, AES.block_size))
 | 
				
			||||||
 | 
					        addr = self.adressGen.get_addresses(start=chunkNum + self.preChunks, count=1, security_level=security_level)['addresses'][0]
 | 
				
			||||||
 | 
					        return self._genBundle(ct_bytes, addr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def append(self, data: bytes, newBlock: bool = False) -> None:
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        if len(self.chunks) and not newBlock:
 | 
				
			||||||
 | 
					            bytesLeft = self.chunks[-1].getBytesLeft()
 | 
				
			||||||
 | 
					            if bytesLeft:
 | 
				
			||||||
 | 
					                leftChunk = data[:bytesLeft]
 | 
				
			||||||
 | 
					                data = data[bytesLeft:]
 | 
				
			||||||
 | 
					                self.chunks[-1].append(leftChunk)
 | 
				
			||||||
 | 
					        while len(data):
 | 
				
			||||||
 | 
					            chunk = data[:CHUNKSIZE]
 | 
				
			||||||
 | 
					            self.chunks.append(BlobChunk(chunk))
 | 
				
			||||||
 | 
					            data = data[CHUNKSIZE:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getChunkLen(self) -> int:
 | 
				
			||||||
 | 
					        return self.preChunks + len(self.chunks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getSize(self) -> int:
 | 
				
			||||||
 | 
					        if len(self.chunks):
 | 
				
			||||||
 | 
					            return self.getChunkLen()*CHUNKSIZE - self.chunks[-1].getBytesLeft()
 | 
				
			||||||
 | 
					        return self.preChunks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self) -> bytes:
 | 
				
			||||||
 | 
					        data = b''
 | 
				
			||||||
 | 
					        for chunk in self.chunks:
 | 
				
			||||||
 | 
					            data += chunk.getData()
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _dump(self) -> str:
 | 
				
			||||||
 | 
					        self.chunks[-1].seal()
 | 
				
			||||||
 | 
					        data = ""
 | 
				
			||||||
 | 
					        for c in range(len(self.chunks)-self.pushedNum):
 | 
				
			||||||
 | 
					            num = c + self.pushedNum
 | 
				
			||||||
 | 
					            data += self._dumpChunk(num, 2)
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fetch(self, security_level=2, skipChunks: int = 0) -> None:
 | 
				
			||||||
 | 
					        chunkNum = self.getChunkLen() + skipChunks
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            key = self._getKey(chunkNum)
 | 
				
			||||||
 | 
					            cipher = AES.new(key[16:][:16], AES.MODE_CBC, key[:16])
 | 
				
			||||||
 | 
					            addr = self.adressGen.get_addresses(start=chunkNum, count=1, security_level=security_level)['addresses'][0]
 | 
				
			||||||
 | 
					            txHash = self.api.find_transactions(tags=[Tag("IOTAFS")], addresses=[addr])["hashes"]
 | 
				
			||||||
 | 
					            if len(txHash)==0:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            bundles = self.api.get_bundles(txHash[0])["bundles"]
 | 
				
			||||||
 | 
					            for bundle in bundles:
 | 
				
			||||||
 | 
					                for tx in bundle.transactions:
 | 
				
			||||||
 | 
					                    # TODO: Can we just strip the 9s and call it a day?
 | 
				
			||||||
 | 
					                    tryteStr = TryteString(str(tx.signature_message_fragment).rstrip("9"))
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        ct_bytes = tryteStr.as_bytes()
 | 
				
			||||||
 | 
					                    except TrytesDecodeError:
 | 
				
			||||||
 | 
					                        ct_bytes = (tryteStr+"9").as_bytes()
 | 
				
			||||||
 | 
					                    self.chunks.append(BlobChunk(unpad(cipher.decrypt(ct_bytes), AES.block_size), True))
 | 
				
			||||||
 | 
					            chunkNum += 1
 | 
				
			||||||
 | 
					        self.pushedNum = len(self.chunks)
 | 
				
			||||||
 | 
					        self.fetched = True
 | 
				
			||||||
 | 
					        self._afterFetch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _afterFetch() -> None:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TangleFileTreeElement(TangleBlob):
 | 
				
			||||||
 | 
					    def __init__(self, token: bytes, iotaApi: Iota) -> None:
 | 
				
			||||||
 | 
					        super(TangleFileTreeElement, self).__init__(token)
 | 
				
			||||||
 | 
					        self.inodes = {}
 | 
				
			||||||
 | 
					        self.atomStack = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _afterFetch(self) -> None:
 | 
				
			||||||
 | 
					        data = msgpack.load(self.read())
 | 
				
			||||||
 | 
					        newAtoms = []
 | 
				
			||||||
 | 
					        for i, elem in enumerate(reversed(data)):
 | 
				
			||||||
 | 
					            atom = Atom()
 | 
				
			||||||
 | 
					            atom.load(elem)
 | 
				
			||||||
 | 
					            if atom.type == "milestone":
 | 
				
			||||||
 | 
					                self._applyMilestone(atom.milestone)
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            newAtoms.append(atom)
 | 
				
			||||||
 | 
					        self.atomStack = len(newAtoms)
 | 
				
			||||||
 | 
					        for atom in reversed(newAtoms):
 | 
				
			||||||
 | 
					            self._applyAtom(atom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _applyMilestone(self, milestone) -> None:
 | 
				
			||||||
 | 
					        self.atomStack = 0
 | 
				
			||||||
 | 
					        self.inodes = {}
 | 
				
			||||||
 | 
					        for stone in milestone:
 | 
				
			||||||
 | 
					            atom = Atom()
 | 
				
			||||||
 | 
					            atom.load(stone)
 | 
				
			||||||
 | 
					            self.inodes[atom.name] = atom
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _applyAtom(self, atom: Atom) -> None:
 | 
				
			||||||
 | 
					        if atom.name in self.inode:
 | 
				
			||||||
 | 
					            self.atomStack += 1
 | 
				
			||||||
 | 
					        self.inodes[atom.name] = atom
 | 
				
			||||||
 | 
					        if atom.type=="dir":
 | 
				
			||||||
 | 
					            self.inodes[atom.name].elem = TangleFileTreeElement(atom.token, self.iotaApi)
 | 
				
			||||||
 | 
					        elif atom.type=="file":
 | 
				
			||||||
 | 
					            self.inodes[atom.name].elem = TangleFile(atom.token, self.iotaApi)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise Exception("How did such an atom get here?")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _newAtom(self, atom: Atom) -> None:
 | 
				
			||||||
 | 
					        self.append(atom.dump())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getNameList(self) -> List(str):
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        return list(self.inodes.keys())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getInode(self, name: str) -> Atom:
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        return self.inodes[name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def mkdir(self, name: str) -> bool:
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        if name in self.getNameList():
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        atom = Atom()
 | 
				
			||||||
 | 
					        atom.type="dir"
 | 
				
			||||||
 | 
					        atom.name = name
 | 
				
			||||||
 | 
					        atom.token = "GENTOKENHERE" # TODO: <
 | 
				
			||||||
 | 
					        atom.milestoneIndex = 0
 | 
				
			||||||
 | 
					        self._newAtom(atom)
 | 
				
			||||||
 | 
					        self._applyAtom(atom)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def mkfile(self, name: str) -> bool:
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        if name in self.getNameList():
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        atom = Atom()
 | 
				
			||||||
 | 
					        atom.type="file"
 | 
				
			||||||
 | 
					        atom.name = name
 | 
				
			||||||
 | 
					        atom.token = "GENTOKENHERE" # TODO: <
 | 
				
			||||||
 | 
					        atom.milestoneIndex = 0
 | 
				
			||||||
 | 
					        self._newAtom(atom)
 | 
				
			||||||
 | 
					        self._applyAtom(atom)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _updateFileSize(self, name: str, size: int):
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        self.inodes[name].size = size
 | 
				
			||||||
 | 
					        self._newAtom(self.inodes[name])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _updateFileToken(self, name: str, token: bytes, size: int):
 | 
				
			||||||
 | 
					        self._requireFetched()
 | 
				
			||||||
 | 
					        self.inodes[name].token = token
 | 
				
			||||||
 | 
					        self.inodes[name].size = size
 | 
				
			||||||
 | 
					        self._newAtom(self.inodes[name])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def performMilestone(self):
 | 
				
			||||||
 | 
					        stones = []
 | 
				
			||||||
 | 
					        for atom in self.inodes:
 | 
				
			||||||
 | 
					            stones.append(atom._dumpAsArray())
 | 
				
			||||||
 | 
					        self.atomStack = 0
 | 
				
			||||||
 | 
					        # TODO: Delimiter ?
 | 
				
			||||||
 | 
					        # TODO: compression ?
 | 
				
			||||||
 | 
					        data = msgpack.dump(stones)
 | 
				
			||||||
 | 
					        self.append(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TangleFile():
 | 
				
			||||||
 | 
					    def __init__(self, name: str, parent: TangleFileTreeElement, iotaApi: Iota) -> None:
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.parent = parent
 | 
				
			||||||
 | 
					        self.reflexiveAtom = parent.inodes[name]
 | 
				
			||||||
 | 
					        self.size = self.reflexiveAtom.size
 | 
				
			||||||
 | 
					        self.token = self.reflexiveAtom.token
 | 
				
			||||||
 | 
					        super(TangleFile, self).__init__(self.token, iotaApi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, offset: int, data: bytes):
 | 
				
			||||||
 | 
					        if offset == self.size:
 | 
				
			||||||
 | 
					            self.append(data)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IotaFs():
 | 
				
			||||||
 | 
					    def __init__(self, token) -> None:
 | 
				
			||||||
 | 
					        self.api = Iota('https://nodes.thetangle.org:443', local_pow=True)
 | 
				
			||||||
 | 
					        self.genesis = TangleFileTreeElement(token, self.api)
 | 
				
			||||||
 | 
					        log("Fetching Genesis...")
 | 
				
			||||||
 | 
					        self.genesis.fetch()
 | 
				
			||||||
 | 
					        log("Retrieving reference to root")
 | 
				
			||||||
 | 
					        if self.genesis.getNameList()!=["/"]:
 | 
				
			||||||
 | 
					            if len(self.genesis.getNameList()):
 | 
				
			||||||
 | 
					                # theres another directory in our genesis chain... WTF?!
 | 
				
			||||||
 | 
					                raise Exception("Corrupted Genesis-Chain:"
 | 
				
			||||||
 | 
					                              + "Unknown records for no root-directory in Genesis Chain: "+str(self.genesis.getNameList()))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # we dont have a root yet, lets create one...
 | 
				
			||||||
 | 
					                log("Unable to reference to root: Creating new root")
 | 
				
			||||||
 | 
					                self.genesis.mkdir("/")
 | 
				
			||||||
 | 
					        log("Successfully Mounted!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def createNewFile(self, name) -> None:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user