Add block "chains" (ex. parent->child mergable blocks)
parent
8846dcc2c6
commit
cdb199e74d
|
@ -81,8 +81,8 @@ class API:
|
||||||
logger.debug('Your web password (KEEP SECRET): ' + logger.colors.underline + self.clientToken)
|
logger.debug('Your web password (KEEP SECRET): ' + logger.colors.underline + self.clientToken)
|
||||||
|
|
||||||
if not debug and not self._developmentMode:
|
if not debug and not self._developmentMode:
|
||||||
hostNums = [random.randint(1, 255), random.randint(1, 255), random.randint(1, 255)]
|
hostOctets = [127, random.randint(0x02, 0xFF), random.randint(0x02, 0xFF), random.randint(0x02, 0xFF)]
|
||||||
self.host = '127.' + str(hostNums[0]) + '.' + str(hostNums[1]) + '.' + str(hostNums[2])
|
self.host = '.'.join(hostOctets)
|
||||||
else:
|
else:
|
||||||
self.host = '127.0.0.1'
|
self.host = '127.0.0.1'
|
||||||
|
|
||||||
|
|
|
@ -655,7 +655,7 @@ class Core:
|
||||||
conn.close()
|
conn.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def insertBlock(self, data, header='txt', sign=False):
|
def insertBlock(self, data, header='txt', sign=False, metadata = {}):
|
||||||
'''
|
'''
|
||||||
Inserts a block into the network
|
Inserts a block into the network
|
||||||
'''
|
'''
|
||||||
|
@ -688,7 +688,11 @@ class Core:
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
|
|
||||||
retData = ''
|
retData = ''
|
||||||
metadata = {'type': header, 'powHash': powHash, 'powToken': powToken}
|
|
||||||
|
metadata['type'] = header
|
||||||
|
metadata['powHash'] = powHash
|
||||||
|
metadata['powToken'] = powToken
|
||||||
|
|
||||||
sig = {}
|
sig = {}
|
||||||
|
|
||||||
metadata = json.dumps(metadata)
|
metadata = json.dumps(metadata)
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Block:
|
||||||
self.signature = None
|
self.signature = None
|
||||||
self.signedData = None
|
self.signedData = None
|
||||||
self.blockFile = None
|
self.blockFile = None
|
||||||
|
self.parent = None
|
||||||
self.bheader = {}
|
self.bheader = {}
|
||||||
self.bmetadata = {}
|
self.bmetadata = {}
|
||||||
|
|
||||||
|
@ -110,6 +111,7 @@ class Block:
|
||||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
||||||
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
||||||
self.bmetadata = json.loads(self.getHeader('meta'))
|
self.bmetadata = json.loads(self.getHeader('meta'))
|
||||||
|
self.parent = (None if not 'parent' in self.getMetadata() else Block(self.getMetadata('parent')))
|
||||||
self.btype = self.getMetadata('type')
|
self.btype = self.getMetadata('type')
|
||||||
self.powHash = self.getMetadata('powHash')
|
self.powHash = self.getMetadata('powHash')
|
||||||
self.powToken = self.getMetadata('powToken')
|
self.powToken = self.getMetadata('powToken')
|
||||||
|
@ -162,7 +164,7 @@ class Block:
|
||||||
blockFile.write(self.getRaw().encode())
|
blockFile.write(self.getRaw().encode())
|
||||||
self.update()
|
self.update()
|
||||||
else:
|
else:
|
||||||
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign)
|
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign, metadata = self.getMetadata())
|
||||||
self.update()
|
self.update()
|
||||||
return self.getHash()
|
return self.getHash()
|
||||||
else:
|
else:
|
||||||
|
@ -226,7 +228,6 @@ class Block:
|
||||||
|
|
||||||
if not key is None:
|
if not key is None:
|
||||||
return self.getHeader()[key]
|
return self.getHeader()[key]
|
||||||
else:
|
|
||||||
return self.bheader
|
return self.bheader
|
||||||
|
|
||||||
def getMetadata(self, key = None):
|
def getMetadata(self, key = None):
|
||||||
|
@ -242,7 +243,6 @@ class Block:
|
||||||
|
|
||||||
if not key is None:
|
if not key is None:
|
||||||
return self.getMetadata()[key]
|
return self.getMetadata()[key]
|
||||||
else:
|
|
||||||
return self.bmetadata
|
return self.bmetadata
|
||||||
|
|
||||||
def getContent(self):
|
def getContent(self):
|
||||||
|
@ -255,6 +255,16 @@ class Block:
|
||||||
|
|
||||||
return str(self.bcontent)
|
return str(self.bcontent)
|
||||||
|
|
||||||
|
def getParent(self):
|
||||||
|
'''
|
||||||
|
Returns the Block's parent Block, or None
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the Block's parent
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.parent
|
||||||
|
|
||||||
def getDate(self):
|
def getDate(self):
|
||||||
'''
|
'''
|
||||||
Returns the date that the block was received, if loaded from file
|
Returns the date that the block was received, if loaded from file
|
||||||
|
@ -345,12 +355,32 @@ class Block:
|
||||||
- btype (str): the type of block to be set to
|
- btype (str): the type of block to be set to
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
- (Block): the block instance
|
- (Block): the Block instance
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.btype = btype
|
self.btype = btype
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def setMetadata(self, key, val):
|
||||||
|
'''
|
||||||
|
Sets a custom metadata value
|
||||||
|
|
||||||
|
Metadata should not store block-specific data structures.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- key (str): the key
|
||||||
|
- val: the value (type is irrelevant)
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the Block instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
if key == 'parent' and (not val is None) and (not val == self.getParent().getHash()):
|
||||||
|
self.setParent(val)
|
||||||
|
else:
|
||||||
|
self.bmetadata[key] = val
|
||||||
|
return self
|
||||||
|
|
||||||
def setContent(self, bcontent):
|
def setContent(self, bcontent):
|
||||||
'''
|
'''
|
||||||
Sets the contents of the block
|
Sets the contents of the block
|
||||||
|
@ -359,13 +389,31 @@ class Block:
|
||||||
- bcontent (str): the contents to be set to
|
- bcontent (str): the contents to be set to
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
- (Block): the block instance
|
- (Block): the Block instance
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.bcontent = str(bcontent)
|
self.bcontent = str(bcontent)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# static
|
def setParent(self, parent):
|
||||||
|
'''
|
||||||
|
Sets the Block's parent
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- parent (Block/str): the Block's parent, to be stored in metadata
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the Block instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
if type(parent) == str:
|
||||||
|
parent = Block(parent, core = self.getCore())
|
||||||
|
|
||||||
|
self.parent = parent
|
||||||
|
self.setMetadata('parent', (None if parent is None else self.getParent().getHash()))
|
||||||
|
return self
|
||||||
|
|
||||||
|
# static functions
|
||||||
|
|
||||||
def getBlocks(type = None, signer = None, signed = None, reverse = False, core = None):
|
def getBlocks(type = None, signer = None, signed = None, reverse = False, core = None):
|
||||||
'''
|
'''
|
||||||
|
@ -421,6 +469,66 @@ class Block:
|
||||||
|
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
|
def merge(child, file = None, maximumFollows = 32, core = None):
|
||||||
|
'''
|
||||||
|
Follows a child Block to its root parent Block, merging content
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- child (str/Block): the child Block to be followed
|
||||||
|
- file (str/file): the file to write the content to, instead of returning it
|
||||||
|
- maximumFollows (int): the maximum number of Blocks to follow
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# validate data and instantiate Core
|
||||||
|
core = (core if not core is None else onionrcore.Core())
|
||||||
|
maximumFollows = max(0, maximumFollows)
|
||||||
|
|
||||||
|
# type conversions
|
||||||
|
if type(child) == str:
|
||||||
|
child = Block(child)
|
||||||
|
if (not file is None) and (type(file) == str):
|
||||||
|
file = open(file, 'ab')
|
||||||
|
|
||||||
|
# only store hashes to avoid intensive memory usage
|
||||||
|
blocks = [child.getHash()]
|
||||||
|
|
||||||
|
# generate a list of parent Blocks
|
||||||
|
while True:
|
||||||
|
# end if the maximum number of follows has been exceeded
|
||||||
|
if len(blocks) - 1 >= maximumFollows:
|
||||||
|
break
|
||||||
|
|
||||||
|
block = Block(blocks[-1], core = core).getParent()
|
||||||
|
|
||||||
|
# end if there is no parent Block
|
||||||
|
if block is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# end if the Block is pointing to a previously parsed Block
|
||||||
|
if block.getHash() in blocks:
|
||||||
|
break
|
||||||
|
|
||||||
|
# end if the block is not valid
|
||||||
|
if not block.isValid():
|
||||||
|
break
|
||||||
|
|
||||||
|
blocks.append(block.getHash())
|
||||||
|
|
||||||
|
buffer = ''
|
||||||
|
|
||||||
|
# combine block contents
|
||||||
|
for hash in blocks:
|
||||||
|
block = Block(hash, core = core)
|
||||||
|
contents = block.getContent()
|
||||||
|
|
||||||
|
if file is None:
|
||||||
|
buffer += contents
|
||||||
|
else:
|
||||||
|
file.write(contents)
|
||||||
|
|
||||||
|
return (None if not file is None else buffer)
|
||||||
|
|
||||||
def exists(hash):
|
def exists(hash):
|
||||||
'''
|
'''
|
||||||
Checks if a block is saved to file or not
|
Checks if a block is saved to file or not
|
||||||
|
|
|
@ -119,7 +119,7 @@ class OnionrTests(unittest.TestCase):
|
||||||
|
|
||||||
def testBlockAPI(self):
|
def testBlockAPI(self):
|
||||||
logger.debug('-'*26 + '\n')
|
logger.debug('-'*26 + '\n')
|
||||||
logger.info('Running BlockAPI test...')
|
logger.info('Running BlockAPI test #1...')
|
||||||
|
|
||||||
content = 'Onionr test block'
|
content = 'Onionr test block'
|
||||||
|
|
||||||
|
@ -134,6 +134,32 @@ class OnionrTests(unittest.TestCase):
|
||||||
logger.warn('Test block content is invalid! (%s != %s)' % (block.getContent(), content))
|
logger.warn('Test block content is invalid! (%s != %s)' % (block.getContent(), content))
|
||||||
self.assertTrue(False)
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
logger.debug('-'*26 + '\n')
|
||||||
|
logger.info('Running BlockAPI test #2...')
|
||||||
|
|
||||||
|
original_content = 'onionr'
|
||||||
|
contents = [original_content[i:i+2] for i in range(0, len(original_content), 2)]
|
||||||
|
contents.reverse()
|
||||||
|
|
||||||
|
blocks = list()
|
||||||
|
parent = None
|
||||||
|
|
||||||
|
for content in contents:
|
||||||
|
block = Block('test', content)
|
||||||
|
block.setParent(parent)
|
||||||
|
parent = block
|
||||||
|
print('block "%s": %s' % (content, block.save()))
|
||||||
|
blocks.append(block)
|
||||||
|
|
||||||
|
child = blocks[-1]
|
||||||
|
|
||||||
|
merged = Block.merge(child)
|
||||||
|
|
||||||
|
print('merged blocks: %s' % merged)
|
||||||
|
|
||||||
|
if merged != original_content:
|
||||||
|
self.assertTrue(False)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
|
||||||
def testBitcoinNode(self):
|
def testBitcoinNode(self):
|
||||||
|
@ -253,6 +279,6 @@ class OnionrTests(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.assertTrue(False)
|
self.assertTrue(False)
|
||||||
else:
|
else:
|
||||||
self.assertTrue(False)
|
self.assertTrue(False) # <- annoying :(
|
||||||
|
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue