Add block "chains" (ex. parent->child mergable blocks)
This commit is contained in:
parent
8846dcc2c6
commit
cdb199e74d
@ -81,8 +81,8 @@ class API:
|
||||
logger.debug('Your web password (KEEP SECRET): ' + logger.colors.underline + self.clientToken)
|
||||
|
||||
if not debug and not self._developmentMode:
|
||||
hostNums = [random.randint(1, 255), random.randint(1, 255), random.randint(1, 255)]
|
||||
self.host = '127.' + str(hostNums[0]) + '.' + str(hostNums[1]) + '.' + str(hostNums[2])
|
||||
hostOctets = [127, random.randint(0x02, 0xFF), random.randint(0x02, 0xFF), random.randint(0x02, 0xFF)]
|
||||
self.host = '.'.join(hostOctets)
|
||||
else:
|
||||
self.host = '127.0.0.1'
|
||||
|
||||
|
@ -655,7 +655,7 @@ class Core:
|
||||
conn.close()
|
||||
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
|
||||
'''
|
||||
@ -688,7 +688,11 @@ class Core:
|
||||
data = data.encode()
|
||||
|
||||
retData = ''
|
||||
metadata = {'type': header, 'powHash': powHash, 'powToken': powToken}
|
||||
|
||||
metadata['type'] = header
|
||||
metadata['powHash'] = powHash
|
||||
metadata['powToken'] = powToken
|
||||
|
||||
sig = {}
|
||||
|
||||
metadata = json.dumps(metadata)
|
||||
|
@ -57,6 +57,7 @@ class Block:
|
||||
self.signature = None
|
||||
self.signedData = None
|
||||
self.blockFile = None
|
||||
self.parent = None
|
||||
self.bheader = {}
|
||||
self.bmetadata = {}
|
||||
|
||||
@ -110,6 +111,7 @@ class Block:
|
||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
||||
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
||||
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.powHash = self.getMetadata('powHash')
|
||||
self.powToken = self.getMetadata('powToken')
|
||||
@ -162,7 +164,7 @@ class Block:
|
||||
blockFile.write(self.getRaw().encode())
|
||||
self.update()
|
||||
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()
|
||||
return self.getHash()
|
||||
else:
|
||||
@ -226,7 +228,6 @@ class Block:
|
||||
|
||||
if not key is None:
|
||||
return self.getHeader()[key]
|
||||
else:
|
||||
return self.bheader
|
||||
|
||||
def getMetadata(self, key = None):
|
||||
@ -242,7 +243,6 @@ class Block:
|
||||
|
||||
if not key is None:
|
||||
return self.getMetadata()[key]
|
||||
else:
|
||||
return self.bmetadata
|
||||
|
||||
def getContent(self):
|
||||
@ -255,6 +255,16 @@ class Block:
|
||||
|
||||
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):
|
||||
'''
|
||||
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
|
||||
|
||||
Outputs:
|
||||
- (Block): the block instance
|
||||
- (Block): the Block instance
|
||||
'''
|
||||
|
||||
self.btype = btype
|
||||
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):
|
||||
'''
|
||||
Sets the contents of the block
|
||||
@ -359,13 +389,31 @@ class Block:
|
||||
- bcontent (str): the contents to be set to
|
||||
|
||||
Outputs:
|
||||
- (Block): the block instance
|
||||
- (Block): the Block instance
|
||||
'''
|
||||
|
||||
self.bcontent = str(bcontent)
|
||||
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):
|
||||
'''
|
||||
@ -421,6 +469,66 @@ class Block:
|
||||
|
||||
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):
|
||||
'''
|
||||
Checks if a block is saved to file or not
|
||||
|
@ -119,7 +119,7 @@ class OnionrTests(unittest.TestCase):
|
||||
|
||||
def testBlockAPI(self):
|
||||
logger.debug('-'*26 + '\n')
|
||||
logger.info('Running BlockAPI test...')
|
||||
logger.info('Running BlockAPI test #1...')
|
||||
|
||||
content = 'Onionr test block'
|
||||
|
||||
@ -134,6 +134,32 @@ class OnionrTests(unittest.TestCase):
|
||||
logger.warn('Test block content is invalid! (%s != %s)' % (block.getContent(), content))
|
||||
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):
|
||||
@ -253,6 +279,6 @@ class OnionrTests(unittest.TestCase):
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
self.assertTrue(False) # <- annoying :(
|
||||
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user