add full-fledged support for token operations that both credit and debit accounts when they are applied. Also, add high-level methods for querying account state

This commit is contained in:
Jude Nelson
2018-04-13 19:16:37 -04:00
parent aa729548c7
commit 86d5102bb0

View File

@@ -442,10 +442,11 @@ class BlockstackDB( virtualchain.StateEngine ):
self.db.commit()
assert block_id in self.vesting, 'BUG: failed to vest at {}'.format(block_id)
# NOTE: tokens vest for the *next* block in order to make the immediately usable
assert block_id+1 in self.vesting, 'BUG: failed to vest at {}'.format(block_id)
self.clear_collisions( block_id )
self.clear_vesting(block_id)
self.clear_vesting(block_id+1)
def log_accept( self, block_id, vtxindex, op, op_data ):
@@ -848,7 +849,9 @@ class BlockstackDB( virtualchain.StateEngine ):
What's the balance of an account?
Aborts if its negative
"""
return namedb_get_account_balance(account)
balance = namedb_get_account_balance(account)
assert isinstance(balance, (int,long)), 'BUG: account balance of {} is {} (type {})'.format(account['address'], balance, type(balance))
return balance
def get_account_delta(self, address, token_type, block_id, vtxindex):
@@ -865,6 +868,15 @@ class BlockstackDB( virtualchain.StateEngine ):
return namedb_get_account_diff(current, prior)
def get_account_history(self, address, block_start, block_end, offset=None, count=None):
"""
Get the history of account transactions over a block range
Returns a dict keyed by blocks, which map to lists of account state transitions
"""
cur = self.db.cursor()
return namedb_get_account_history(cur, address, block_start, block_end, offset=offset, count=count)
def get_name_at( self, name, block_number, include_expired=False ):
"""
Generate and return the sequence of of states a name record was in
@@ -901,17 +913,7 @@ class BlockstackDB( virtualchain.StateEngine ):
cur = self.db.cursor()
name_hist = namedb_get_history( cur, name )
return name_hist
def get_name_history_blocks( self, name ):
"""
Get the blocks at which this name was affected
Returns [block heights]
"""
cur = self.db.cursor()
update_points = namedb_get_blocks_with_name_ops( cur, name, FIRST_BLOCK_MAINNET, self.lastblock )
return update_points
def get_all_ops_at( self, block_number, offset=None, count=None, include_history=None, restore_history=None ):
"""
@@ -1572,7 +1574,6 @@ class BlockstackDB( virtualchain.StateEngine ):
canonical_op = None
op_type_str = None # for debugging
opcode = accepted_nameop.get('opcode', None)
history_id = None
try:
assert opcode is not None, "Undefined op '%s'" % accepted_nameop['op']
@@ -1588,20 +1589,21 @@ class BlockstackDB( virtualchain.StateEngine ):
elif opcode in OPCODE_CREATION_OPS:
# creation
history_id_key = state_create_get_history_id_key( accepted_nameop )
history_id = accepted_nameop[history_id_key]
canonical_op = self.commit_state_create( accepted_nameop, current_block_number )
op_type_str = "state_create"
elif opcode in OPCODE_TRANSITION_OPS:
# transition
history_id_key = state_transition_get_history_id_key( accepted_nameop )
history_id = accepted_nameop[history_id_key]
canonical_op = self.commit_state_transition( accepted_nameop, current_block_number )
op_type_str = "state_transition"
elif opcode in OPCODE_TOKEN_OPS:
# token operation
canonical_op = self.commit_token_operation(accepted_nameop, current_block_number)
op_type_str = "token_operation"
else:
raise Exception("Unknown operation '%s'" % opcode)
raise Exception("Unknown operation {}".format(opcode))
if canonical_op is None:
log.error("FATAL: no canonical op generated (for {})".format(op_type_str))
@@ -1612,9 +1614,9 @@ class BlockstackDB( virtualchain.StateEngine ):
return consensus_op
def commit_account_debit(self, nameop, account_payment_info, current_block_number, current_vtxindex, current_txid):
def commit_account_debit(self, opcode, account_payment_info, current_block_number, current_vtxindex, current_txid):
"""
Given the account info set by a state-create or state-transition,
Given the account info set by a state-create or state-transition or a token-operation,
debit the relevant account.
Do not call this directly.
@@ -1629,7 +1631,7 @@ class BlockstackDB( virtualchain.StateEngine ):
if account_address is not None and account_payment is not None and account_token_type is not None:
# sanity check
try:
assert account_payment > 0, 'Non-positive account payment {}'.format(account_payment)
assert account_payment >= 0, 'Negative account payment {}'.format(account_payment)
assert self.is_token_type_supported(account_token_type), 'Unsupported token type {}'.format(account_token_type)
except Exception as e:
log.exception(e)
@@ -1644,7 +1646,44 @@ class BlockstackDB( virtualchain.StateEngine ):
log.fatal("Failed to debit address {} {} {}".format(account_address, account_payment, account_token_type))
os.abort()
log.debug("COMMIT DEBIT ACCOUNT {} for {} units of {}(s) for {}".format(account_address, account_payment, account_token_type, nameop.get('opcode', None)))
log.debug("COMMIT DEBIT ACCOUNT {} for {} units of {}(s) for {}".format(account_address, account_payment, account_token_type, opcode))
return True
def commit_account_credit(self, opcode, account_credit_info, current_block_number, current_vtxindex, current_txid):
"""
Given the account info set by a state-create or state-transition or a token-operation,
debit the relevant account.
Do not call this directly.
Return True on success
Abort on error
"""
account_address = account_credit_info['address']
account_credit = account_credit_info['amount']
account_token_type = account_credit_info['type']
if account_address is not None and account_credit is not None and account_token_type is not None:
# sanity check
try:
assert account_credit >= 0, 'Non-positive account credit {}'.format(account_credit)
assert self.is_token_type_supported(account_token_type), 'Unsupported token type {}'.format(account_token_type)
except Exception as e:
log.exception(e)
log.fatal("Sanity check failed")
os.abort
# have to debit this account
cur = self.db.cursor()
rc = namedb_account_credit(cur, account_address, account_token_type, account_credit, current_block_number, current_vtxindex, current_txid)
if not rc:
traceback.print_stack()
log.fatal("Failed to debit address {} {} {}".format(account_address, account_credit, account_token_type))
os.abort()
log.debug("COMMIT CREDIT ACCOUNT {} for {} units of {}(s) for {}".format(account_address, account_credit, account_token_type, opcode))
return True
@@ -1661,6 +1700,15 @@ class BlockstackDB( virtualchain.StateEngine ):
log.error("FATAL: borrowing violation: not a read-write connection")
traceback.print_stack()
os.abort()
opcode = None
try:
opcode = nameop.get('opcode')
assert opcode is not None, 'BUG: no preorder opcode'
except Exception as e:
log.exception(e)
log.error("FATAL: no opcode in preorder")
os.abort()
# did we pay any tokens for this state?
account_payment_info = state_preorder_get_account_payment_info(nameop)
@@ -1682,7 +1730,7 @@ class BlockstackDB( virtualchain.StateEngine ):
os.abort()
# debit tokens, if present
self.commit_account_debit(nameop, account_payment_info, current_block_number, nameop['vtxindex'], nameop['txid'])
self.commit_account_debit(opcode, account_payment_info, current_block_number, nameop['vtxindex'], nameop['txid'])
self.db.commit()
return commit_preorder
@@ -1839,10 +1887,67 @@ class BlockstackDB( virtualchain.StateEngine ):
self.db.rollback()
os.abort()
self.commit_account_debit(nameop, account_payment_info, current_block_number, transition['vtxindex'], transition['txid'])
self.commit_account_debit(opcode, account_payment_info, current_block_number, transition['vtxindex'], transition['txid'])
return canonical_op
def commit_token_operation(self, token_op, current_block_number):
"""
Commit a token operation that debits one account and credits another
Returns the new canonicalized record (with all compatibility quirks preserved)
DO NOT CALL THIS DIRECTLY
"""
# have to have read-write disposition
if self.disposition != DISPOSITION_RW:
log.error("FATAL: borrowing violation: not a read-write connection")
traceback.print_stack()
os.abort()
cur = self.db.cursor()
opcode = token_op.get('opcode', None)
clean_token_up = self.sanitize_op(token_op)
try:
assert token_operation_is_valid(token_op), 'Invalid token operation'
assert opcode is not None, 'No opcode given'
assert 'txid' in token_op, 'No txid'
assert 'vtxindex' in token_op, 'No vtxindex'
except Exception as e:
log.exception(e)
log.error('FATAL: failed to commit token operation')
self.db.rollback()
os.abort()
table = token_operation_get_table(token_op)
account_payment_info = token_operation_get_account_payment_info(token_op)
account_credit_info = token_operation_get_account_credit_info(token_op)
# fields must be set
try:
for key in account_payment_info:
assert account_payment_info[k] is not None, 'BUG: payment info key {} is None'.format(key)
for key in account_credit_info:
assert account_credit_info[k] is not None, 'BUG: credit info key {} is not None'.format(key)
# NOTE: do not check token amount and type, since in the future we want to support converting
# between tokens
except Exception as e:
log.exception(e)
log.error("FATAL: invalid token debit or credit info")
os.abort()
self.log_accept(current_block_number, token_op['vtxindex'], token_op['op'], token_op)
# NOTE: this code is single-threaded and must remain so
self.commit_account_debit(token_op, account_payment_info, current_block_number, token_op['vtxindex'], token_op['txid'])
self.commit_account_credit(token_op, account_credit_info, current_block_number, token_op['vtxindex'], token_op['txid'])
return clean_token_op
def commit_account_vesting(self, block_height):
"""