def check_tx_create_contract(tx: TX, include_block: Block): if len(tx.inputs) == 0 or len(tx.outputs) == 0: raise BlockChainError('No inputs or outputs.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('create contract tx is bytes msg.') elif V.BLOCK_CONTRACT_PREFIX is None: raise BlockChainError('Not set contract prefix ?') elif V.BLOCK_CONTRACT_PREFIX == V.BLOCK_PREFIX: raise BlockChainError('normal prefix same with contract prefix.') # GAS量チェック estimate_gas = tx.getsize() + C.CONTRACT_CREATE_FEE if estimate_gas > tx.gas_amount: raise BlockChainError('Insufficient gas [{}>{}]'.format( estimate_gas, tx.gas_amount)) # Contractをデコードできるか c_address, c_bin, c_cs = bjson.loads(tx.message) binary2contract(c_bin) # ContractStorageの初期値チェック if c_cs: for k, v in c_cs.items(): if not isinstance(k, bytes) or not isinstance(v, bytes): raise BlockChainError('cs format is wrong. {}'.format(c_cs)) if not is_address(c_address, V.BLOCK_CONTRACT_PREFIX): raise BlockChainError('Is not contract address. {}'.format(c_address)) # 既に登録されていないかチェック cs = get_contract_storage(c_address, include_block) if cs.version != 0: raise BlockChainError('Already created contract. {}'.format(tx))
def check_tx_start_contract(start_tx: TX, include_block: Block): # 共通チェック c_address, c_data, c_args, c_redeem = bjson.loads(start_tx.message) if not is_address(c_address, V.BLOCK_CONTRACT_PREFIX): raise BlockChainError('Is not contract address. {}'.format(c_address)) elif not (c_args is None or isinstance(c_args, list) or isinstance(c_args, tuple)): raise BlockChainError('c_args is {}'.format(type(c_args))) elif not is_address(c_redeem, V.BLOCK_PREFIX): raise BlockChainError('Is not redeem address. {}'.format(c_redeem)) elif start_tx.gas_price < V.COIN_MINIMUM_PRICE: raise BlockChainError('GasPrice is too low. [{}<{}]'.format( start_tx.gas_price, V.COIN_MINIMUM_PRICE)) elif start_tx.gas_amount < V.CONTRACT_MINIMUM_AMOUNT: raise BlockChainError('GasAmount is too low. [{}<{}]'.format( start_tx.gas_amount, V.CONTRACT_MINIMUM_AMOUNT)) # Block内チェック if include_block: # 同一のStartTXを示すFinishTXが存在しないかチェック count = 0 for finish_tx in include_block.txs: if finish_tx.type != C.TX_FINISH_CONTRACT: continue c_status, c_start_hash, c_diff = bjson.loads(finish_tx.message) if c_start_hash != start_tx.hash: continue count += 1 if count == 0: raise BlockChainError( 'Not found FinishTX on block. {}'.format(start_tx)) if count > 1: raise BlockChainError( 'Find some FinishTX on block. {}'.format(count)) else: c_address, c_method, c_args, c_redeem = bjson.loads(start_tx.message) get_contract_binary(c_address) if P.VALIDATOR_OBJ and im_a_validator(include_block): P.VALIDATOR_OBJ.put_unvalidated(start_tx) logging.debug("Add validation que {}".format(start_tx))
def signature_check(tx): need_cks = set() for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found input tx {}'.format(hexlify(txhash).decode())) address, coin_id, amount = input_tx.outputs[txindex] if is_address(address, V.BLOCK_PREFIX): need_cks.add(address) # 通常のアドレスのみ else: raise BlockChainError('Not common address {} {}.'.format(address, tx)) signed_cks = get_signed_cks(tx) if need_cks != signed_cks: raise BlockChainError('Signature verification failed. [{}={}]'.format(need_cks, signed_cks))
def update_unspents_txs(): global unspents_txs c = 50 while previous_block is None and 0 < c: sleep(0.2) c -= 1 previous_height = previous_block.height proof_txs = list() all_num = 0 for address, height, txhash, txindex, coin_id, amount in get_unspents_iter( ): if height is None: continue if coin_id != 0: continue if not (previous_height + 1 > height + C.MATURE_HEIGHT): continue if not is_address(address, prefix=V.BLOCK_PREFIX): continue if amount < 100000000: continue if staking_limit < all_num: logging.debug("Unspents limit reached, skip by {} limits.".format( staking_limit)) break all_num += 1 proof_tx = TX( tx={ 'type': C.TX_POS_REWARD, 'inputs': [(txhash, txindex)], 'outputs': [(address, 0, 0)], 'gas_price': 0, 'gas_amount': 0, 'message_type': C.MSG_NONE, 'message': b'' }) proof_tx.height = previous_height + 1 proof_tx.pos_amount = amount proof_txs.append(proof_tx) unspents_txs = proof_txs return all_num, len(proof_txs)
def batch_apply(self, force=False): # 無チェックで挿入するから要注意 if not force and self.cashe_limit > len(self.chain): return list() # cashe許容量を上回っているので記録 self.db.batch_create() logging.debug("Start batch apply. chain={} force={}".format(len(self.chain), force)) best_chain = self.best_chain.copy() batch_count = self.batch_size batched_blocks = list() with closing(create_db(V.DB_ACCOUNT_PATH)) as db: cur = db.cursor() # DO NOT USE FOR WRITE! try: block = None while batch_count > 0 and len(best_chain) > 0: batch_count -= 1 block = best_chain.pop() # 古いものから順に batched_blocks.append(block) self.db.write_block(block) # Block assert len(block.txs) > 0, "found no tx in {}".format(block) for tx in block.txs: self.db.write_tx(tx) # TX # inputs for index, (txhash, txindex) in enumerate(tx.inputs): # DataBase内でのみのUsedIndexを取得 usedindex = self.db.read_usedindex(txhash) if txindex in usedindex: raise BlockBuilderError('Already used index? {}:{}' .format(hexlify(txhash).decode(), txindex)) usedindex.add(txindex) self.db.write_usedindex(txhash, usedindex) # UsedIndex update input_tx = tx_builder.get_tx(txhash) address, coin_id, amount = input_tx.outputs[txindex] if config['full_address_index'] or is_address(ck=address, prefix=V.BLOCK_CONTRACT_PREFIX)\ or read_address2user(address=address, cur=cur): # 必要なAddressのみ self.db.write_address_idx(address, txhash, txindex, coin_id, amount, True) # outputs for index, (address, coin_id, amount) in enumerate(tx.outputs): if config['full_address_index'] or is_address(ck=address, prefix=V.BLOCK_CONTRACT_PREFIX) \ or read_address2user(address=address, cur=cur): # 必要なAddressのみ self.db.write_address_idx(address, tx.hash, index, coin_id, amount, False) # TXの種類による追加操作 if tx.type == C.TX_GENESIS: pass elif tx.type == C.TX_TRANSFER: pass elif tx.type == C.TX_POW_REWARD: pass elif tx.type == C.TX_POS_REWARD: pass elif tx.type == C.TX_MINT_COIN: mint_id, params, setting = bjson.loads(tx.message) self.db.write_coins(coin_id=mint_id, txhash=tx.hash, params=params, setting=setting) elif tx.type == C.TX_VALIDATOR_EDIT: c_address, new_address, flag, sig_diff = bjson.loads(tx.message) self.db.write_validator(c_address=c_address, new_address=new_address, flag=flag, tx=tx, sign_diff=sig_diff) elif tx.type == C.TX_CONCLUDE_CONTRACT: c_address, start_hash, c_storage = bjson.loads(tx.message) start_tx = tx_builder.get_tx(txhash=start_hash) dummy, c_method, redeem_address, c_args = bjson.loads(start_tx.message) self.db.write_contract(c_address=c_address, start_tx=start_tx, finish_hash=tx.hash, message=(c_method, c_args, c_storage)) # block挿入終了 self.best_chain = best_chain self.root_block = block self.db.batch_commit() self.save_starter() # root_blockよりHeightの小さいBlockを消す for blockhash, block in self.chain.copy().items(): if self.root_block.height >= block.height: del self.chain[blockhash] logging.debug("Success batch {} blocks, root={}." .format(len(batched_blocks), self.root_block)) # アカウントへ反映↓ user_account.new_batch_apply(batched_blocks) return batched_blocks # [<height=n>, <height=n+1>, .., <height=n+m>] except Exception as e: self.db.batch_rollback() logging.warning("Failed batch block builder. '{}'".format(e), exc_info=True) return list()
def check_tx_validator_edit(tx: TX, include_block: Block): # common check if not (len(tx.inputs) > 0 and len(tx.inputs) > 0): raise BlockChainError('No inputs or outputs.') elif tx.message_type != C.MSG_BYTE: raise BlockChainError('validator_edit_tx is bytes msg.') elif V.BLOCK_CONTRACT_PREFIX is None: raise BlockChainError('Not set contract prefix ?') elif V.BLOCK_CONTRACT_PREFIX == V.BLOCK_PREFIX: raise BlockChainError('normal prefix same with contract prefix.') # message try: c_address, new_address, flag, sig_diff = bjson.loads(tx.message) except Exception as e: raise BlockChainError('BjsonError: {}'.format(e)) # inputs/outputs address check for txhash, txindex in tx.inputs: input_tx = tx_builder.get_tx(txhash) if input_tx is None: raise BlockChainError('Not found input tx.') address, coin_id, amount = input_tx.outputs[txindex] if address != c_address: raise BlockChainError('1 Not contract address. {}'.format(address)) for address, coin_id, amount in tx.outputs: if address != c_address: raise BlockChainError('2 Not contract address. {}'.format(address)) # check new_address v_before = get_validator_object(c_address=c_address, best_block=include_block, stop_txhash=tx.hash) if new_address: if not is_address(ck=new_address, prefix=V.BLOCK_PREFIX): raise BlockChainError('new_address is normal prefix.') elif flag == F_NOP: raise BlockChainError('input new_address, but NOP.') if v_before.index == -1: # create validator for the first time if new_address is None: raise BlockChainError('Not setup new_address.') elif flag != F_ADD: raise BlockChainError('Need to add new_address flag.') elif sig_diff != 1: raise BlockChainError('sig_diff is 1.') else: # edit already created validator next_validator_num = len(v_before.validators) # Note: Add/Remove after next_require_num = v_before.require + sig_diff if flag == F_ADD: if new_address is None: raise BlockChainError('Not setup new_address.') elif new_address in v_before.validators: raise BlockChainError('Already included new_address.') next_validator_num += 1 elif flag == F_REMOVE: if new_address is None: raise BlockChainError('Not setup new_address.') elif new_address not in v_before.validators: raise BlockChainError('Not include new_address.') elif len(v_before.validators) < 2: raise BlockChainError('validator is now only {}.'.format(len(v_before.validators))) next_validator_num -= 1 elif flag == F_NOP: if new_address is not None: raise BlockChainError('Input new_address?') else: raise BlockChainError('unknown flag {}.'.format(flag)) # sig_diff check if not (0 < next_require_num <= next_validator_num): raise BlockChainError('sig_diff check failed, 0 < {} <= {}.' .format(next_require_num, next_validator_num)) contract_required_gas_check(tx=tx, v=v_before, extra_gas=C.VALIDATOR_EDIT_GAS) contract_signature_check(extra_tx=tx, v=v_before, include_block=include_block)
def send_many(sender, send_pairs, cur, fee_coin_id=0, gas_price=None, msg_type=C.MSG_NONE, msg_body=b'', f_balance_check=True, retention=10800): assert isinstance(sender, int), 'Sender is user id.' assert 0 < len(send_pairs), 'Empty send_pairs.' # send_pairs check movements = UserCoins() outputs = list() coins = CoinObject() for address, coin_id, amount in send_pairs: assert isinstance( address, str) and len(address) == 40, 'Recipient is 40 letter string.' assert isinstance(coin_id, int) and isinstance( amount, int), 'CoinID, amount is int.' assert is_address( address, V.BLOCK_PREFIX), 'Not correct format {}'.format(address) coins[coin_id] += amount outputs.append((address, coin_id, amount)) movements[sender] -= coins movements[C.ANT_OUTSIDE] += coins # tx now = int(time.time() - V.BLOCK_GENESIS_TIME) tx = TX( tx={ 'version': __chain_version__, 'type': C.TX_TRANSFER, 'time': now, 'deadline': now + retention, 'inputs': list(), 'outputs': outputs, 'gas_price': gas_price or V.COIN_MINIMUM_PRICE, 'gas_amount': 1, 'message_type': msg_type, 'message': msg_body }) tx.gas_amount = tx.getsize() # fill unspents input_address = fill_inputs_outputs(tx, cur, fee_coin_id, additional_fee=0) # account check fee_coins = CoinObject(coin_id=fee_coin_id, amount=tx.gas_price * tx.gas_amount) if f_balance_check: # 残高が十分にあるかチェック send_coins = CoinObject() for address, coin_id, amount in send_pairs: send_coins[coin_id] += amount check_enough_amount(sender, send_coins, fee_coins) if sender in (C.ANT_OUTSIDE, C.ANT_RESERVED): # 内部アカウントは不可 raise BlockChainError('Not allowed inner account.') # replace dummy address replace_redeem_dummy_address(tx, cur) # setup signature tx.serialize() setup_signature(tx, input_address) movements[sender] -= fee_coins movements[C.ANT_OUTSIDE] += fee_coins insert_log(movements, cur, tx.type, tx.time, tx.hash) return tx
def send(self, from_id, to_address, mosaics, msg=b'', only_check=True, balance_check=True, encrypted=False, db=None): if db is None: db = self.db assert self.sk is not None, 'You need sk if you use \"send\"' to_address = to_address.replace('-', '') if to_address == self.ck: raise AccountError("You send to and receive to same address.") elif not is_address(to_address): raise AccountError('Not correct address format.') for mosaic in mosaics: self._check_expire_mosaic(mosaic, db) if encrypted: to_pk = self.nem.get_account_info(ck=to_address)['account']['publicKey'] if to_pk is None: raise AccountError('You send encrypt msg to Account that have never send before.') # Generally cannot convert CK to PK. msg = encrypt(self.sk, to_pk, msg) msg_type = 2 else: msg_type = 1 fee = self.nem.estimate_levy_fee(mosaics) fee = DictMath.add(fee, self.nem.estimate_msg_fee(msg)) fee = DictMath.add(fee, self.nem.estimate_send_fee(mosaics)) tx_dict = self.nem.mosaic_transfer(self.pk, to_address, mosaics, msg, msg_type) tb = TransactionBuilder() tx_hex = tb.encode(tx_dict) sign_raw = sign(msg=unhexlify(tx_hex.encode()), sk=self.sk, pk=self.pk) sign_hex = hexlify(sign_raw).decode() if only_check: balance = self.balance(from_id) need_amount = DictMath.add(fee, mosaics) send_ok = DictMath.all_plus_amount(DictMath.sub(balance, need_amount)) # only_check=False return sending info, NOT send return fee, send_ok, tx_dict, tx_hex, sign_hex else: with self.transaction: self.refresh(db=db) with db as conn: balance = self.balance(from_id) need_amount = DictMath.add(fee, mosaics) if balance_check and not DictMath.all_plus_amount(DictMath.sub(balance, need_amount)): need = {m: a for m, a in DictMath.sub(balance, need_amount).items() if a < 0} raise AccountError('Not enough balance on ID:%d, %s' % (from_id, need)) tx_hash = self.nem.transaction_announce(tx_hex, sign_hex) outgoing_many = list() for mosaic in need_amount: # height, time is None amount = need_amount[mosaic] value = self.get_value(mosaic, amount, db=db) price = self.get_price(mosaic, db=db) outgoing_many.append(( unhexlify(tx_hash.encode()), None, from_id, mosaic, amount, value, price, None )) conn.executemany(""" INSERT INTO `outgoing_table` VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, outgoing_many) conn.commit() threading.Thread(target=self._send, name='Wait', args=(tx_hash,), daemon=False).start() return tx_hash
import time from nem_ed25519.key import secret_key, public_key, get_address, is_address start = time.time() result = list() COUNT = 10 for i in range(COUNT): sk = secret_key() pk = public_key(sk) ck = get_address(pk) if not is_address(ck): raise Exception('not correct key') result.append((sk, pk, ck)) print((time.time() - start) * 1000 // COUNT, "mS/create_pair") print("\ntry check") sk = '78f8932df54d22319a16dc4940c269205ae0946f98d38ef30aea488a47426153' pk = '77041bfb4b6afebc31aaab7b02d68e577fe069524b3c661c804b42ef381f717b' ck = 'NBOGOGSENUPBFMAPTGHVI4UIAQPVSNKJLWUVHBED' assert pk == public_key(sk), 'Not correct sk' assert ck == get_address(pk), 'Not correct pk' print("all ok.")