EX1A-3 HLDRS RTS.5 10 a18-15736_1ex1a3hldrsrtsd5.htm SMART CONTRACT FOR THE STACKS TOKEN

Exhibit 3.5

 

Rights of Tokenholders

 

Tokenholders have received token allocations in the Stacks genesis block.

Tokenholders can do the following things with their tokens:

 

·                  Unlock fixed quantities of tokens over an agreed-upon amount of time (though no action is necessary on their part to receive them).

·                  Purchase digital assets in Blockstack — i.e. names and namespaces.

·                  Transfer tokens to other principals.

 

Preliminary materials: the database table schemas for all tokens, and for tokens that will be unlocked:

 

# The variable ‘BLOCKSTACK_DB_ACCOUNTS_TABLE_SCRIPT’ encodes the sequence of

# SQL commands to instantiate an “accounts” table an an “accounts_vesting” table.

# The “accounts” table provides a double-entry ledger of all debits and

# credits for all accounts, and “accounts_vesting” provides a list of all the

# token amounts and block heights that unlock for shareholders (“vesting” is a

# misnomer — in the legal sense, tokens are unlocked, not vested).

 

BLOCKSTACK_DB_ACCOUNTS_TABLE_SCRIPT = “““

CREATE TABLE accounts( address TEXT NOT NULL,

 

type TEXT NOT NULL,                       

— what kind of token? STACKs, etc.

 

 

credit_value TEXT NOT NULL,           

— always increases, encoded as a TEXT to avoid overflow (unit value)

debit_value TEXT NOT NULL,         

— always increases, encoded as a TEXT to avoid overflow (unit value)

 

lock_transfer_block_id INTEGER NOT NULL, — block height at which it becomes possible for this account to send tokens

 

receive_whitelisted INTEGER NOT NULL,— for now, only certain accounts can receive tokens (this will be 1 by default — all accounts can receive tokens)

 

 

metadata TEXT, — user-programmable field (e.g. stores hash of legal contract that owns the address)

 

— where in the blockchain this occurred

— NOTE: account operations may be inserted as a result of processing another operation (like buying a name).

— if so, then this information will point to the history snapshot of that operation.

— built-in operations (like token vesting) will ‘occur’ at vtxindex = 0 (i.e. as part of the block’s “coinbase”)

block_id INTEGER NOT NULL, — block height of debit/credit

txid TEXT NOT NULL, — transaction ID on-chain

vtxindex INTEGER NOT NULL, — location of tx in the block

PRIMARY KEY(address,block_id,txid,vtxindex,type) );

”””

 

BLOCKSTACK_DB_SCRIPT += BLOCKSTACK_DB_ACCOUNTS_TABLE_SCRIPT

# this table encodes the points in time where each account will unlock tokens,

# and it encodes what amount will be unlcoked.

BLOCKSTACK_DB_ACCOUNT_VESTING_TABLE_SCRIPT = “““

CREATE TABLE account_vesting( address TEXT NOT NULL, — account address

type TEXT NOT NULL, — type of token (e.g. STACKS)

vesting_value TEXT NOT NULL, — value to vest, encoded as a TEXT to avoid overflow (unit value, e.g. microSTACKs)

block_id INTEGER NOT NULL, — block at which these tokens are credited

 

PRIMARY KEY(address,type,block_id) );

 

 

The code that unlocks tokens is presented below, with annotations beginning with #:

 

def namedb_accounts_vest(cur, block_height):

 

“““

Note that “vest” is a misnomer here. The tokens are already granted;

this code snippit simply unlocks them. Tokens are NOT vested, since

they cannot be reclaimed.

”””

 

# Step 1: get a list of all accounts that are supposed to unlock tokens

# at the given block_height.

sql = ‘SELECT * FROM account_vesting WHERE block_id = ?’

args = (block_height,)

 

vesting_rows = namedb_query_execute(cur, sql, args)

rows = []

for row in vesting_rows:

tmp = {}

tmp.update(row)

rows.append(tmp)

 

# Step 2: for each account, insert an “account credit” in the accounts table

# that records that the account received an allocation of tokens as part of

# processing this block. Because the unlocking does not correspond to a specific

# on-chain transaction, a sentinel transaction ID will be used to record the

# “location” on the chain where this happens (specifically, as far as the token

# system is concerned, each account receives its next grant at the “beginning”

# of each block).

for row in rows:

 

 

# each ‘row’ is an account, identified uniquely by its ‘address’

addr = row[‘address’]

token_type = row[‘type’]

token_amount = row[‘vesting_value’]

 

log.debug(“Vest {} with {} {}”.format(addr, token_amount, token_type))

# the sentinel transaction ID

fake_txid = namedb_vesting_txid(addr, token_type, token_amount, block_height)

 

# credit the account with the amount of tokens that unlock at this block

res = namedb_account_credit(cur, addr, token_type, token_amount, block_height, 0, fake_txid)

if not res:

# if anything goes wrong, crash immediately. It is better for the whole

# network to crash and deny future transactions until the bug is fixed

# than it is to possibly tolerate the misappropriation of shareholder

# tokens.

traceback.print_stack()

log.fatal(‘Failed to vest {} {} to {}’.format(token_amount, token_type, addr))

os.abort()

 

return True

 

The code that accepts payments for namespaces is here:

 

def check( state_engine, nameop, block_id, checked_ops ):

 

“““

Given a NAMESPACE_PREORDER nameop, see if we can process it.

It must be unqiue.

 

 

Return True if accepted.

Return False if not.

”””

 

namespace_id_hash = nameop[‘preorder_hash’]

consensus_hash = nameop[‘consensus_hash’]

token_fee = nameop[‘token_fee’]

 

# cannot be preordered already

if not state_engine.is_new_namespace_preorder( namespace_id_hash ):

log.warning(“Namespace preorder ‘%s’ already in use” % namespace_id_hash)

return False

 

# has to have a reasonable consensus hash

if not state_engine.is_consensus_hash_valid( block_id, consensus_hash ):

valid_consensus_hashes = state_engine.get_valid_consensus_hashes( block_id )

log.warning(“Invalid consensus hash ‘%s’: expected any of %s” % (consensus_hash, “,”.join( valid_consensus_hashes )))

return False

 

# has to have paid a (Bitcoin) fee, even if nominal

if not ‘op_fee’ in nameop:

log.warning(“Missing namespace preorder fee”)

return False

 

# paid to the right burn address

if nameop[‘burn_address’] != BLOCKSTACK_BURN_ADDRESS:

log.warning(“Invalid burn address: expected {}, got {}”.format(BLOCKSTACK_BURN_ADDRESS, nameop[‘burn_address’]))

return False

 

# token burn fee must be present, if we’re in the right epoch for it

epoch_features = get_epoch_features(block_id)

if EPOCH_FEATURE_STACKS_BUY_NAMESPACES in epoch_features:

# must pay in STACKs

 

 

if ‘token_fee’ not in nameop:

log.warning(“Missing token fee”)

return False

 

token_fee = nameop[‘token_fee’]

token_address = nameop[‘address’]

token_type = TOKEN_TYPE_STACKS

 

# was a token fee paid?

if token_fee is None:

log.warning(“No tokens paid by this NAMESPACE_PREORDER”)

 

return False

 

# does this account have enough balance?

account_info = state_engine.get_account(token_address, token_type)

if account_info is None:

log.warning(“No account for {} ({})”.format(token_address, token_type))

return False

account_balance = state_engine.get_account_balance(account_info)

 

assert isinstance(account_balance, (int,long)), ‘BUG:

account_balance of {} is {} (type {})’.format(token_address, account_balance, type(account_balance))

assert isinstance(token_fee, (int,long)), ‘BUG: token_fee is {} (type {})’.format(token_fee, type(token_fee))

 

if account_balance < token_fee:

# can’t afford

log.warning(“Account {} has balance {} {}, but needs to pay {} {}”.format(token_address, account_balance, token_type, token_fee, token_type))

return False

 

# debit this account when we commit

state_preorder_put_account_payment_info(nameop, token_address, token_type, token_fee)

 

 

# NOTE: must be a string, to avoid overflow

nameop[‘token_fee’] = ‘{}’.format(token_fee)

nameop[‘token_units’] = TOKEN_TYPE_STACKS

 

else:

# must pay in BTC

# not paying in tokens, but say so!

state_preorder_put_account_payment_info(nameop, None, None, None)

nameop[‘token_fee’] = ‘0’

nameop[‘token_units’] = ‘BTC’

return True

 

The number of Stacks tokens paid is determined from the on-chain transaction, extracted with this method:

 

def parse( bin_payload, block_height ):

 

“““

NOTE: the first three bytes will be missing

 

wire format (Pre-STACKs Phase 1)

 

 

magic op hash(ns_id,script_pubkey,reveal_addr) consensus hash

 

wire format (Post-STACKs phase 1)

 

 

magic op hash(ns_id,script_pubkey,reveal_addr) consensus hash token fee (big-endian)

 

 

Returns {

‘opcode’: ...

‘preorder_hash’: ...

‘consensus_hash’: ...

‘token_fee’: ...

}

”””

 

if len(bin_payload) < LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]:

log.warning(“Invalid namespace preorder payload length %s” % len(bin_payload))

return None

 

namespace_id_hash = bin_payload[ :LENGTHS[‘preorder_name_hash’] ]

 

consensus_hash = bin_payload[ LENGTHS[‘preorder_name_has h’]: LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]]

 

tokens_burned = None

 

epoch_features = get_epoch_features(block_height)

 

if len(bin_payload) > LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]:

 

if EPOCH_FEATURE_STACKS_BUY_NAMESPACES not in epoch_features:

 

# not allowed—we can’t use tokens in this epoch

log.warning(“Invalid payload {}: expected {} bytes”.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]))

return None

 

if len(bin_payload) != LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]:

 

# not allowed—invalid length

log.warning(“Invalid payload {}: expected {} bytes”.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]))

return None

 

 

bin_tokens_burned = bin_payload[LENGTHS[‘preorder_name _hash’] + LENGTHS[‘consensus_hash’]: LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]]

tokens_burned = int(bin_tokens_burned.encode(‘hex’), 16)

else:

# only allow the absence of the tokens field if we’re in a pre-STACKs epoch

if EPOCH_FEATURE_STACKS_BUY_NAMESPACES in epoch_features:

 

# not allowed—we need the stacks token field

log.warning(‘Invalid payload {}: expected {} bytes’.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]))

return None

 

namespace_id_hash = hexlify( namespace_id_hash )

consensus_hash = hexlify( consensus_hash )

 

return {

‘opcode’: ‘NAMESPACE_PREORDER’,

‘preorder_hash’: namespace_id_hash,

‘consensus_hash’: consensus_hash,

‘token_fee’: tokens_burned

}

 

Similarly, the code for a name preorder can require the principal who sent the transaction to pay in Stacks (if determined by the namespace in which the name will be registered):

 

def check( state_engine, nameop, block_id, checked_ops ):

“““

Verify that a preorder of a name at a particular block number is well-formed

 

NOTE: these *can’t* be incorporated into namespace-imports, since we have no way of knowning which namespace the nameop belongs to (it is blinded until registration). But that’s okay—we don’t need to preorder names during a namespace import, because we will only accept names sent from the importer until the NAMESPACE_REVEAL operation is sent.

 

 

Return True if accepted

Return False if not.

”””

 

from .register import get_num_names_owned

 

preorder_name_hash = nameop[‘preorder_hash’]

consensus_hash = nameop[‘consensus_hash’]

sender = nameop[‘sender’]

 

token_fee = nameop[‘token_fee’]

token_type = nameop[‘token_units’]

token_address = nameop[‘address’]

 

# must be unique in this block

# NOTE: now checked externally in the @state_preorder decorator

 

# must be unique across all pending preorders

if not state_engine.is_new_preorder( preorder_name_hash ):

log.warning(“Name hash ‘%s’ is already preordered” % preorder_name_hash )

return False

 

# must have a valid consensus hash

if not state_engine.is_consensus_hash_valid( block_id, consensus_hash ):

log.warning(“Invalid consensus hash ‘%s’“ % consensus_hash )

return False

 

# sender must be beneath quota

num_names = get_num_names_owned( state_engine, checked_ops, sender )

if num_names >= MAX_NAMES_PER_SENDER:

log.warning(“Sender ‘%s’ exceeded name quota of %s” % (sender, MAX_NAMES_PER_SENDER ))

 

 

return False

 

# burn fee must be present

if not ‘op_fee’ in nameop:

log.warning(“Missing preorder fee”)

return False

 

epoch_features = get_epoch_features(block_id)

if EPOCH_FEATURE_NAMEOPS_COST_TOKENS in epoch_features and token_type is not None and token_fee is not None:

# does this account have enough balance?

account_info = state_engine.getaccount(token_address, token_type)

if account_info is None:

log.warning(“No account for {} ({})”.format(token_ address, token_type))

return False

 

account_balance = state_engine.get_account_balance(account_info)

 

assert isinstance(account_balance, (int,long)), ‘BUG: account_balance of {} is {} (type {})’.format(token_address, account_balance, type(account_balance))

assert isinstance(token_fee, (int,long)), ‘BUG: token_fee is {} (type {})’.format(token_fee, type(token_fee))

 

if account_balance < token_fee:

# can’t afford

log.warning(“Account {} has balance {} {}, but needs to pay {} {}”.format(token_address, account_balance, token_type, token_fee, token_type))

return False

 

# must be the black hole address, regardless of namespace version (since we don’t yet support pay-stacks-to-namespace-creator)

if nameop[‘burn_address’] != BLOCKSTACK_BURN_ADDRESS:

# not sent to the right address

 

 

log.warning(‘Preorder burned to {}, but expected {}’.format(nameop[‘burn_address’], BLOCKSTACK_BURN_ADDRESS))

return False

 

# for now, this must be Stacks

if nameop[‘token_units’] != TOKEN_TYPE_STACKS:

# can’t use any other token (yet)

log.warning(‘Preorder burned unrecognized token unit “{}”’.format(nameop[‘token_units’]))

return False

 

# debit this account when we commit

state_preorder_put_account_payment_info(nameop, token_address, token_type, token_fee)

 

# NOTE: must be a string, to avoid overflow

nameop[‘token_fee’] = ‘{}’.format(token_fee)

 

else:

# not paying in tokens, but say so!

state_preorder_put_account_payment_info(nameop, None, None, None)

nameop[‘token_fee’] = ‘0’

nameop[‘token_units’] = ‘BTC’

 

return True

 

The number of Stacks tokens is encoded in the NAME_PREORDER transaction onchain, and decoded with this method:

 

def parse(bin_payload, block_height):

 

“““

Parse a name preorder.

NOTE: bin_payload *excludes* the leading 3 bytes (magic + op) returned by build.

 

Record format:

 

 

 

 

 

magic op hash(name.ns_id,script_pubkey,register_addr) consensus hash

 

Record format when burning STACKs (STACKS Phase 1):

 

 

magic op hash(name.ns_id,script_pubkey,register_addr) consensus hash tokens to burn (big-endian) token units (0-padded)

 

Returns {

opcode: NAME_PREORDER,

preorder_hash: the hash of the name, scriptPubKey, and register address

consensus_hash: the consensus_hash

token_fee: the amount of tokens to burn (will be None if not given)

token_units: the type of tokens to burn (will be None if not given)

}

”””

 

epoch_features = get_epoch_features(block_height)

 

if len(bin_payload) < LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]:

log.warning(“Invalid payload {}: expected at least {} bytes”.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]))

return None

 

name_hash = hexlify( bin_payload[0:LENGTHS[‘preorder_name_hash’]] )

consensus_hash = hexlify( bin_payload[LENGTHS[‘preorder_name_hash’]: LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]] )

 

 

tokens_burned = None

token_units = None

 

if len(bin_payload) > LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]:

# only acceptable if there’s a token burn

if EPOCH_FEATURE_NAMEOPS_COST_TOKENS not in epoch_feat ures:

 

# not enabled yet

log.warning(“Invalid payload {}: expected {} bytes”.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]))

return None

 

if len(bin_payload) != LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’] + LENGTHS[‘namespace_id’]:

# invalid

log.warning(“Invalid payload {}: expected {} bytes”.format(bin_payload.encode(‘hex’), LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]))

return None

 

at_tokens_burnt = LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’]

at_token_units = LENGTHS[‘preorder_name_hash’] + LENGTHS[‘consensus_hash’] + LENGTHS[‘tokens_burnt’]

bin_tokens_burnt = bin_payload[at_tokens_burnt: at_tokens_burnt + LENGTHS[‘tokens_burnt’]]

bin_token_units = bin_payload[at_token_units: at_token_units + LENGTHS[‘namespace_id’]]

 

tokens_burned = int(bin_tokens_burnt.encode(‘hex’), 16)

token_units = bin_token_units.strip(‘\x00’)

 

return {

‘opcode’: ‘NAME_PREORDER’,

 

 

‘preorder_hash’: name_hash,

‘consensus_hash’: consensus_hash,

‘token_fee’: tokens_burned,

‘token_units’: token_units,

}

 

As a consequence of paying for a name or a namespace, the decoded nameop is extended with payment information via state_preorder_put_account_payment_info(), as follows:

 

def state_preorder_put_account_payment_info( nameop, account_addr, token_type, amount ):

 

“““

Call this in a @state_create-decorated method.

Identifies the account that must be debited.

”””

 

assert amount is None or isinstance(amount, (int,long)), ‘Amount is {} (type {})’.format(amount, type(amount))

assert account_addr is None or isinstance(account_addr, (str,unicode))

assert token_type is None or isinstance(token_type, (str,unicode))

nameop[‘_ _account_payment_info_ _’] = {

‘address’: str(account_addr) if account_addr is not None else None,

‘type’: str(token_type) if token_type is not None else None,

‘amount’: int(amount) if amount is not None else None

}

 

This information, in turn, is consumed by the name database in order to update the accounts table to debit the account holder the requisite amount of Stacks in order to carry out the operation (e.g. insert the preorder into the preorders table):

 

def commit_state_preorder( self, nameop, current_block_number ):

 

“““

Commit a state preorder (works for namespace_preorder and name_preorder),

DO NOT CALL THIS DIRECTLY

”””

 

 

# have to have read-write disposition

if self.disposition != DISPOSITION_RW:

log.error(“FATAL: borrowing violation: not a readwrite 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)

 

cur = self.db.cursor()

 

# cannot have collided

if BlockstackDB.nameop_is_collided( nameop ):

log.debug(“Not commiting ‘%s’, since it collided” % nameop)

self.log_reject(current_block_number, nameop[‘vtxindex’], nameop[‘op’], nameop)

return []

 

self.log_accept( current_block_number, nameop[‘vtxindex’], nameop[‘op’], nameop )

 

commit_preorder = self.sanitize_op( nameop )

rc = namedb_preorder_insert( cur, commit_preorder )

if not rc:

 

 

log.error(“FATAL: failed to commit preorder ‘%s’ ” % commit_preorder[‘preorder_hash’] )

os.abort()

 

# debit tokens, if present

self.commit_account_debit(opcode, account_payment_info, current_block_number, nameop[‘vtxindex’], nameop[‘txid’])

 

self.db.commit()

return commit_preorder

 

The actual debit is carried out in the commit_account_debit() method, which in turn invokes the namedb_account_debit() method to alter the state of the accounts table:

 

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 or a token-operation, debit the relevant account.

 

Do not call this directly.

 

Return True on success

Abort on error

”””

 

account_address = account_payment_info[‘address’]

account_payment = account_payment_info[‘amount’]

account_token_type = account_payment_info[‘type’]

 

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, ‘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)

 

 

log.fatal(“Sanity check failed”)

os.abort()

 

# have to debit this account

cur = self.db.cursor()

rc = namedb_account_debit(cur, account_address, account_token_type, account_payment, current_block_number, current_vtxindex, current_txid)

if not rc:

traceback.print_stack()

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, opcode))

return True

 

def namedb_account_debit(cur, account_addr, token_type, amount, block_id, vtxindex, txid):

 

“““

Debit an account at a particular point in time by the given amount.

Insert a new history entry for the account into the accounts table.

 

The account must exist

 

Abort the program if the account balance goes negative, or the count does not exist

”””

 

account = namedb_get_account(cur, account_addr, token_type)

 

if account is None:

traceback.print_stack()

log.fatal(‘Account {} does not exist’.format(account_addr))

os.abort()

 

 

new_credit_value = account[‘credit_value’]

new_debit_value = account[‘debit_value’] + amount

 

# sanity check

if new_debit_value > new_credit_value:

traceback.print_stack()

log.fatal(‘Account {} for “{}” tokens overdrew (debits = {}, credits = {})’.format(account_addr, token_type, new_debit_value, new_credit_value))

os.abort()

 

new_balance = new_credit_value - new_debit_value

log.debug(“Account balance of units of ‘{}’ for {} is now {}”.format(token_type, account_addr, new_balance))

 

res = namedb_account_transaction_save(cur, account_addr, token_type, new_credit_value, new_debit_value, block_id, vtxindex, txid, account)

if not res:

traceback.print_stack()

log.fatal(‘Failed to save new account state for {}’.format(account_addr))

os.abort()

 

return True

 

def namedb_account_transaction_save(cur, address, token_type, new_credit_value, new_debit_value, block_id, vtxindex, txid, existing_account):

 

“““

Insert the new state of an account at a particular point in time.

 

The data must be for a never-before-seen (txid,block_id,vtxindex) set in the accounts table, but must correspond to an entry in the history table.

 

If existing_account is not None, then copy all other remaining fields from it.

 

 

Return True on success

Raise an Exception on error

”””

 

if existing_account is None:

existing_account = {}

 

accounts_insert = {

‘address’: address,

‘type’: token_type,

‘credit_value’: ‘{}’.format(new_credit_value),

‘debit_value’: ‘{}’.format(new_debit_value),

‘lock_transfer_block_id’: existing_account.get(‘lock_transfer_block_id’, 0), # unlocks immediately if the account doesn’t exist

‘receive_whitelisted’: existing_account.get(‘receive_whitelisted’, True), # new accounts are whitelisted by default (for now)

‘metadata’: existing_account.get(‘metadata’, None),

‘block_id’: block_id,

‘txid’: txid,

‘vtxindex’: vtxindex

}

 

try:

query, values = namedb_insert_prepare(cur, accounts_insert, ‘accounts’)

except Exception as e:

log.exception(e)

log.fatal(‘FATAL: failed to append account history record for {} at ({},{})’.format(address, block_id, vtxindex))

os.abort()

 

namedb_query_execute(cur, query, values)

return True