mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-03-27 22:46:07 +08:00
355 lines
12 KiB
Python
355 lines
12 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Blockstack-client
|
|
~~~~~
|
|
copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
|
|
copyright: (c) 2016 by Blockstack.org
|
|
|
|
This file is part of Blockstack-client.
|
|
|
|
Blockstack-client is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Blockstack-client is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import copy
|
|
|
|
from ..config import *
|
|
import copy
|
|
|
|
import preorder
|
|
import register
|
|
import transfer
|
|
import update
|
|
import revoke
|
|
import nameimport
|
|
import namespacepreorder
|
|
import namespacereveal
|
|
import namespaceready
|
|
import announce
|
|
import binascii
|
|
|
|
from .preorder import make_transaction as tx_preorder
|
|
from .register import make_transaction as tx_register
|
|
from .update import make_transaction as tx_update
|
|
from .transfer import make_transaction as tx_transfer
|
|
from .revoke import make_transaction as tx_revoke
|
|
from .namespacepreorder import make_transaction as tx_namespace_preorder
|
|
from .namespacereveal import make_transaction as tx_namespace_reveal
|
|
from .namespaceready import make_transaction as tx_namespace_ready
|
|
from .nameimport import make_transaction as tx_name_import
|
|
from .announce import make_transaction as tx_announce
|
|
|
|
from .preorder import get_fees as fees_preorder
|
|
from .register import get_fees as fees_registration
|
|
from .update import get_fees as fees_update
|
|
from .transfer import get_fees as fees_transfer
|
|
from .revoke import get_fees as fees_revoke
|
|
from .namespacepreorder import get_fees as fees_namespace_preorder
|
|
from .namespacereveal import get_fees as fees_namespace_reveal
|
|
from .namespaceready import get_fees as fees_namespace_ready
|
|
from .nameimport import get_fees as fees_name_import
|
|
from .announce import get_fees as fees_announce
|
|
|
|
from .preorder import build as build_preorder
|
|
from .register import build as build_registration
|
|
from .update import build as build_update
|
|
from .transfer import build as build_transfer
|
|
from .revoke import build as build_revoke
|
|
from .namespacepreorder import build as build_namespace_preorder
|
|
from .namespacereveal import build as build_namespace_reveal
|
|
from .namespaceready import build as build_namespace_ready
|
|
from .nameimport import build as build_name_import
|
|
from .announce import build as build_announce
|
|
|
|
from .preorder import snv_consensus_extras as preorder_consensus_extras
|
|
from .register import snv_consensus_extras as register_consensus_extras
|
|
from .update import snv_consensus_extras as update_consensus_extras
|
|
from .transfer import snv_consensus_extras as transfer_consensus_extras
|
|
from .revoke import snv_consensus_extras as revoke_consensus_extras
|
|
from .nameimport import snv_consensus_extras as name_import_consensus_extras
|
|
from .namespacepreorder import snv_consensus_extras as namespace_preorder_consensus_extras
|
|
from .namespacereveal import snv_consensus_extras as namespace_reveal_consensus_extras
|
|
from .namespaceready import snv_consensus_extras as namespace_ready_consensus_extras
|
|
from .announce import snv_consensus_extras as announce_consensus_extras
|
|
|
|
# NOTE: these all have the same signatures
|
|
SNV_CONSENSUS_EXTRA_METHODS = {
|
|
"NAME_PREORDER": preorder_consensus_extras,
|
|
"NAME_REGISTRATION": register_consensus_extras,
|
|
"NAME_RENEWAL": register_consensus_extras,
|
|
"NAME_UPDATE": update_consensus_extras,
|
|
"NAME_TRANSFER": transfer_consensus_extras,
|
|
"NAME_REVOKE": revoke_consensus_extras,
|
|
"NAME_IMPORT": name_import_consensus_extras,
|
|
"NAMESPACE_PREORDER": namespace_preorder_consensus_extras,
|
|
"NAMESPACE_REVEAL": namespace_reveal_consensus_extras,
|
|
"NAMESPACE_READY": namespace_ready_consensus_extras,
|
|
"ANNOUNCE": announce_consensus_extras
|
|
}
|
|
|
|
def nameop_is_history_snapshot( history_snapshot ):
|
|
"""
|
|
Given a history entry, verify that it is a history snapshot.
|
|
It must have all consensus fields.
|
|
Return True if so.
|
|
Raise an exception of it doesn't.
|
|
"""
|
|
|
|
# sanity check: each mutate field in the operation must be defined in op_data, even if it's null.
|
|
missing = []
|
|
|
|
assert 'op' in history_snapshot.keys(), "no op given"
|
|
|
|
opcode = op_get_opcode_name(history_snapshot['op'])
|
|
assert opcode is not None, "unrecognized op '%s'" % history_snapshot['op']
|
|
|
|
op = history_snapshot['op']
|
|
|
|
consensus_fields = OPFIELDS[op]
|
|
for field in consensus_fields:
|
|
if field not in history_snapshot.keys():
|
|
missing.append( field )
|
|
|
|
assert len(missing) == 0, ("operation '%s' is missing the following fields: %s" % (opcode, ",".join(missing)))
|
|
return True
|
|
|
|
|
|
def nameop_history_extract( history_rows ):
|
|
"""
|
|
Given the rows of history for a name, collapse
|
|
them into a history dictionary.
|
|
Return a dict of:
|
|
{
|
|
block_id: [
|
|
{ ... historical data ...
|
|
txid:
|
|
vtxindex:
|
|
op:
|
|
opcode:
|
|
}, ...
|
|
],
|
|
...
|
|
}
|
|
|
|
Raise on failure to parse
|
|
"""
|
|
|
|
history = {}
|
|
for history_row in history_rows:
|
|
|
|
block_id = history_row['block_id']
|
|
data_json = history_row['history_data']
|
|
hist = json.loads( data_json )
|
|
|
|
hist['opcode'] = op_get_opcode_name(hist['op'])
|
|
|
|
if history.has_key( block_id ):
|
|
history[ block_id ].append( hist )
|
|
else:
|
|
history[ block_id ] = [ hist ]
|
|
|
|
return history
|
|
|
|
|
|
def nameop_restore_from_history( name_rec, name_history, block_id ):
|
|
"""
|
|
Given a name or a namespace record (`name_rec`), replay its
|
|
history diffs (`name_history`) "back in time" to a particular block
|
|
number (`block_id`).
|
|
|
|
Return the sequence of states the name record went
|
|
through at that block number, starting from the beginning
|
|
of the block.
|
|
|
|
Return None if the record does not exist at that point in time
|
|
|
|
The returned records will *not* have a 'history' key.
|
|
"""
|
|
|
|
block_history = list( reversed( sorted( name_history.keys() ) ) )
|
|
|
|
historical_rec = copy.deepcopy( name_rec )
|
|
if 'history' in historical_rec:
|
|
del historical_rec['history']
|
|
|
|
if len(block_history) == 0:
|
|
# there is no history here...
|
|
try:
|
|
assert nameop_is_history_snapshot( historical_rec ), "No history for incomplete name"
|
|
return [historical_rec]
|
|
except Exception, e:
|
|
log.exception(e)
|
|
log.debug("\n%s" % (json.dumps(historical_rec, indent=4, sort_keys=True)))
|
|
log.error("FATAL: tried to restore history for incomplete record")
|
|
os.abort()
|
|
|
|
if block_id > block_history[0]:
|
|
# current record is valid
|
|
return [historical_rec]
|
|
|
|
if block_id < name_rec['block_number']:
|
|
# doesn't yet exist
|
|
return None
|
|
|
|
# find the latest block prior to block_number
|
|
last_block = len(block_history)
|
|
for i in xrange( 0, len(block_history) ):
|
|
if block_id >= block_history[i]:
|
|
last_block = i
|
|
break
|
|
|
|
i = 0
|
|
while i < last_block:
|
|
|
|
try:
|
|
diff_list = list( reversed( name_history[ block_history[i] ] ) )
|
|
except:
|
|
print json.dumps( name_history[block_history[i]], indent=4, sort_keys=True )
|
|
raise
|
|
|
|
for di in xrange(0, len(diff_list)):
|
|
diff = diff_list[di]
|
|
|
|
if diff.has_key('history_snapshot'):
|
|
# wholly new state
|
|
historical_rec = copy.deepcopy( diff )
|
|
del historical_rec['history_snapshot']
|
|
|
|
else:
|
|
# delta in current state
|
|
# no matter what, 'block_number' cannot be altered (unless it's a history snapshot)
|
|
if diff.has_key('block_number'):
|
|
del diff['block_number']
|
|
|
|
historical_rec.update( diff )
|
|
|
|
i += 1
|
|
|
|
# if this isn't the earliest history element, and the next-earliest
|
|
# one (at last block) has multiple entries, then generate the sequence
|
|
# of updates for all but the first one. This is because all but the
|
|
# first one were generated in the same block (i.e. the block requested).
|
|
updates = [ copy.deepcopy( historical_rec ) ]
|
|
|
|
if i < len(block_history):
|
|
|
|
try:
|
|
diff_list = list( reversed( name_history[ block_history[i] ] ) )
|
|
except:
|
|
print json.dumps( name_history[block_history[i]] )
|
|
raise
|
|
|
|
if len(diff_list) > 1:
|
|
for diff in diff_list[:-1]:
|
|
|
|
# no matter what, 'block_number' cannot be altered
|
|
if diff.has_key('block_number'):
|
|
del diff['block_number']
|
|
|
|
if diff.has_key('history_snapshot'):
|
|
# wholly new state
|
|
historical_rec = copy.deepcopy( diff )
|
|
del historical_rec['history_snapshot']
|
|
|
|
else:
|
|
# delta in current state
|
|
historical_rec.update( diff )
|
|
|
|
updates.append( copy.deepcopy(historical_rec) )
|
|
|
|
return list( reversed( updates ) )
|
|
|
|
|
|
def nameop_snv_consensus_extra_quirks( extras, namerec, block_id ):
|
|
"""
|
|
Given the set of arguments to snv_consensus_extras, apply any
|
|
op-specific quirks that are needed to preserve backwards compatibility
|
|
"""
|
|
|
|
last_creation_op = namerec.get('last_creation_op', None)
|
|
last_creation_opcode = None
|
|
|
|
if last_creation_op is not None:
|
|
last_creation_opcode = op_get_opcode_name(last_creation_op)
|
|
|
|
if last_creation_opcode is None:
|
|
if namerec['op'] == NAME_IMPORT:
|
|
# this is the first-ever import
|
|
last_creation_opcode = 'NAME_IMPORT'
|
|
|
|
elif namerec['op'] == NAME_PREORDER:
|
|
# this is the first-ever preorder
|
|
last_creation_opcode = 'NAME_PREORDER'
|
|
|
|
log.debug("apply SNV QURIKS on %s at %s (created with %s)" % (namerec.get('name', "UNKNOWN"), block_id, last_creation_opcode))
|
|
|
|
if namerec.has_key('name') and last_creation_opcode == 'NAME_IMPORT':
|
|
log.debug("apply SNV QUIRK on %s: %s --> %s" % (namerec.get('name', "UNKNOWN"), namerec['op_fee'], float(namerec['op_fee'])))
|
|
extras['op_fee'] = float(namerec['op_fee'])
|
|
|
|
return extras
|
|
|
|
|
|
def nameop_snv_consensus_extra( op_name, prev_name_rec, prev_block_id ):
|
|
"""
|
|
Derive any missing consensus-generating fields from the
|
|
fields of a name record (since some of them
|
|
are dynamically generated when the operation
|
|
is discovered). This method is used for
|
|
calculating prior operations from name records
|
|
for SNV.
|
|
|
|
The given name record is the name record as it was when
|
|
prev_block_id was processed. The 'vtxindex' field within
|
|
the name record indicates which the transaction at which
|
|
it existed. I.e., the given name record is in the state
|
|
it was in at (prev_block_id, prev_name_rec['vtxindex']).
|
|
|
|
Return the extra conesnsus fields on success.
|
|
Return None on error.
|
|
"""
|
|
|
|
global SNV_CONSENSUS_EXTRA_METHODS
|
|
|
|
if op_name not in SNV_CONSENSUS_EXTRA_METHODS.keys():
|
|
raise Exception("No such operation '%s'" % op_name)
|
|
|
|
method = SNV_CONSENSUS_EXTRA_METHODS[op_name]
|
|
extras = method( prev_name_rec, prev_block_id, None )
|
|
extras = nameop_snv_consensus_extra_quirks( extras, prev_name_rec, prev_block_id )
|
|
return extras
|
|
|
|
|
|
def nameop_restore_snv_consensus_fields( name_rec, block_id ):
|
|
"""
|
|
Given a name record at a given point in time, ensure
|
|
that all of its consensus fields are present.
|
|
Because they can be reconstructed directly from the record,
|
|
but they are not always stored in the db, we have to do so here.
|
|
"""
|
|
|
|
opcode_name = op_get_opcode_name( name_rec['op'] )
|
|
assert opcode_name is not None, "Unrecognized opcode '%s'" % name_rec['op']
|
|
|
|
ret_op = nameop_snv_consensus_extra( opcode_name, name_rec, block_id )
|
|
|
|
if ret_op is None:
|
|
raise Exception("Failed to derive extra consensus fields for '%s'" % opcode_name)
|
|
|
|
ret_op['opcode'] = opcode_name
|
|
|
|
merged_op = copy.deepcopy( name_rec )
|
|
merged_op.update( ret_op )
|
|
|
|
return merged_op
|