def worker(self): self.chain.Pause() BuildAndRun(self.args, wallet=self.wallet, verbose=True) self.chain.Resume() avm_path = self.scPath.replace('.py', '.avm') self.args[0] = avm_path from_addr = None current_height = 0 code = LoadContract(args=self.args) # /scripts/sc.avm 0710 02 True False False if code: script = generate_deploy_script( code.Script, "myTestSmartContract", # name "test", # version "", # author "", # email "", # description code.ContractProperties, code.ReturnTypeBigInteger, code.ParameterList) if script is not None: tx, fee, results, num_ops = test_invoke(script, self.wallet, [], from_addr=from_addr) if tx is not None and results is not None: print("Test deploy invoke successful") print("Deploy Invoke TX GAS cost: %s " % (tx.Gas.value / Fixed8.D)) print("Deploy Invoke TX Fee: %s " % (fee.value / Fixed8.D)) print("-------------------------") while not self.isSynced: self.show_state() time.sleep(1) result = InvokeContract(self.wallet, tx, Fixed8.Zero(), from_addr=from_addr) print("Result: ", result.ToJson(), self.isSynced) print("Result: ", tx.ToJson()) current_height = self.chain.Height + 1 print("Script:", script) # we expect the transaction to be included in the next block: while current_height > self.chain.Height: self.show_state() time.sleep(5) self.twist.stop()
class ItemStore(object): """ Houses pretty much everything pertaining to the actual faucet """ Wallet = None app = Klein() j2_env = Environment(loader=FileSystemLoader('templates'), autoescape=select_autoescape(['html']), trim_blocks=True) OPERATION = 'transfer' def __init__(self): """ initializes the database by calling the create_tables method; redeclares the contract hash (for redundancy); initializes variables that are needed to get the faucet instance running as well as pass data to the smart contract; sets up the wallet; checks the height of the blockchain and compares it to the block height of the wallet, if they are not equivalent, it processes new blocks until they are """ self._create_tables() self.params = [] self.invoke_args = [] self.sent_tx = None self.token_name = os.environ.get('TOKEN_NAME') self.token_symbol = os.environ.get('TOKEN_SYMBOL') self.token_script_hash = os.environ.get('TOKEN_SCRIPT_HASH') if self.token_name is None or self.token_symbol is None or self.token_script_hash is None: raise Exception( 'Please set TOKEN_NAME, TOKEN_SYMBOL, and TOKEN_SCRIPT_HASH in ./config/nep5-token.json' ) wallet_path = os.environ.get('FAUCET_WALLET_PATH') wallet_pwd = to_aes_key(os.environ.get('FAUCET_WALLET_PASSWORD')) self.faucet_wallet_addr = os.environ.get('FAUCET_WALLET_ADDRESS') if wallet_pwd is None or wallet_path is None or self.faucet_wallet_addr is None: raise Exception( 'Please set FAUCET_WALLET_PATH, FAUCET_WALLET_PASSWORD, and FAUCET_WALLET_ADDRESS ' 'in ./config/environment.json') self.wallet = UserWallet.Open(path=wallet_path, password=wallet_pwd) dbloop = task.LoopingCall( self.wallet.ProcessBlocks) # specifies what function to call dbloop.start( .1 ) # every 0.1 seconds, self.wallet.ProcessBlocks function is called self.wallet.Rebuild() self.wallet._current_height = 100000 # is this arbitrary or is there a specific reason to set at this height? logger.info(f'created wallet: {self.wallet.ToJson()}') @staticmethod def _create_tables(): """ Creates the FaucetRequest and IPRequest tables in the DynamoDB database if they don't already exist """ if not FaucetRequest.exists(): FaucetRequest.create_table(wait=True) if not IPRequest.exists(): IPRequest.create_table(wait=True) def _get_context(self): """ Function loads all of the NEP5 tokens stored in the faucet's wallet, then gets the amount of NEP5 tokens with the specified script hash :return: a json object containing the blockchain's block height, amount of user-specified NEP5 tokens in the wallet, and the current wallet height """ tokens = self.wallet.LoadNEP5Tokens( ) # loads all nep-5 tokens from the faucet's wallet token_balance = self.wallet.GetTokenBalance( token=tokens.get(self.token_script_hash.encode('utf-8'))) if token_balance is None: token_balance = BigInteger(0) return { 'message': 'Welcome to NEP-5 Faucet', 'faucet_wallet': self.faucet_wallet_addr, 'height': Blockchain.Default().Height, 'wallet_height': self.wallet.WalletHeight, f'{self.token_symbol}': int(token_balance), 'token_name': self.token_name, 'token_symbol': self.token_symbol, 'token_script_hash': self.token_script_hash } def _make_tx(self, address_to): """ Function that creates the parameters for the NEP-5 transfer function and then calls it, returning the transaction info :param address_to: address to send the tokens to :return: transaction info """ drip_amount = BigInteger(10000) amount = BigInteger( drip_amount * 100000000 ) # must multiply by Fixed8.D and must be BigInteger object self.invoke_args.clear() self.params.clear( ) # make sure that invoke_args and params lists are empty scripthash_from = Helper.AddrStrToScriptHash( address=self.faucet_wallet_addr).ToArray() scripthash_to = address_to self.invoke_args.append(scripthash_from) self.invoke_args.append(scripthash_to) self.invoke_args.append(amount) self.params = [ self.token_script_hash.encode('utf-8'), self.OPERATION, self.invoke_args ] nonce = struct.pack('>i', int(time())) # convert the int to a big endian int # the int(time()) is the time in seconds since the epoch (01/01/1970 for UNIX systems) # the nonce was used to append to the TransactionAttribute so that each transaction has slightly # different data. Otherwise, each transaction would constantly be given the same transaction hash because # all of their parameters are the same # TransactionAttributeUsage.Remark is b'\xf0' = 240 invoke_attributes = [ TransactionAttribute(usage=TransactionAttributeUsage.Remark, data=nonce) ] # the following test invokes the contract (useful to ensure that noting is wrong) tx, fee, results, num_ops = TestInvokeContract( wallet=self.wallet, args=self.params, from_addr=self.faucet_wallet_addr, min_fee=Fixed8.Zero(), invoke_attrs=invoke_attributes) # the following is just used for logging info if tx is not None and results is not None: logger.info('\n------------------------') for item in results: logger.info(f'Results: {str(item)}') if not len(str(item)) % 2: try: logger.info( f'Hex decode: {binascii.unhexlify(str(item))}') except Exception: logger.warning('Not hex encoded') logger.info(f'Invoke TX gas cost: {(tx.Gas.value / Fixed8.D)}') logger.info(f'Invoke TX Fee: {(fee.value / Fixed8.D)}') logger.info('Relaying invocation tx now') logger.info('------------------------\n') # invoke the contract self.sent_tx = InvokeContract(wallet=self.wallet, tx=tx, fee=Fixed8.Zero(), from_addr=self.faucet_wallet_addr) sleep( 3 ) # allow a few seconds for the contract to be invoked and return the tx info is actually passed return self.sent_tx @app.route('/') @app.route('/index') def index(self, request): """ index page for the faucet site :param request: klein Request object that gets passed into every route function :return: the rendered index template containing the context """ ctx = self._get_context() if ctx[f'{self.token_symbol}'] < 10000: logger.warning('NO ASSETS AVAILABLE') ctx['error'] = True ctx['message'] = 'NO ASSETS AVAILABLE. Come back later.' ctx['come_back'] = True output = self.j2_env.get_template('index.html').render(ctx) return output @app.route('/ask', methods=['POST']) def ask_for_assets(self, request): """ This function processes the user input; creates and saves the user's IP address and time of their visit and their wallet address and time of their visit; if the user's information checks out and they haven't visited too many times, then it will send their information to the _make_tx_ function. Upon success, it will redirect the user to the success page and display the transaction info :param request: klein request object that gets passed into every route function :return: upon success, returns a Deferred that already had '.callback(result)' called upon failure, returns the index page with some error displayed """ self.sent_tx = None ctx = self._get_context() ctx['error'] = False proceed = True step = 0 iprequest_item = None faucetrequest_item = None address_to = None now = datetime.now() past_week = now - timedelta(days=7) #next_week = now + timedelta(days=7) # for testing epochTs = time( ) # the time() is the time in seconds since the epoch (01/01/1970 for UNIX systems) week_in_sec = 60 * 60 * 24 * 7 # 60 seconds * 60 minutes * 24 hours * 7 days expire_date = epochTs + week_in_sec if ctx[f'{self.token_symbol}'] < 10000: request.redirect('/') return succeed(None) try: if b'address_to' in request.args: address_to = request.args.get(b'address_to')[0].decode( 'utf-8').strip() ctx['address_to'] = address_to client = None if (proceed is True) & (step == 0): # for IPRequest #client = str(request.getClientAddress().host) # gets the IPv4 address client = str( request.getHeader(key='x-real-ip') ) # gets the IPv4 address if behind NGINX server iprequest_item = IPRequest( ip_address=client, last_visited=now, ttl=expire_date) # creates IPRequest item count = 0 # the following gets all requests from this ip address in the past week for item in IPRequest.query( hash_key=client, range_key_condition=IPRequest.last_visited > past_week, limit=10): count += 1 logger_request.info(f'IPRequest TOTAL: {count}') if count >= 3: # if user(s) from this ip has requested >= 3 times in the past week, proceed = False # stop progress for the program ctx['message'] = 'You have requested too many times for this time period. ' \ 'Try again next week.' # display error message ctx['error'] = True else: # else go to the next step step += 1 elif (proceed is True) & (step == 0): ctx['error'] = True ctx['message'] = 'Request failed. Please try again.' if (proceed is True) & (step == 1): # for FaucetRequest faucetrequest_item = FaucetRequest( wallet_address=address_to, last_visited=now, ttl=expire_date) # creates FaucetRequest item count = 0 # the following gets all requests from this wallet address in the past week for item in FaucetRequest.query( hash_key=address_to, range_key_condition=FaucetRequest.last_visited > past_week, limit=10): count += 1 logger_request.info(f'FaucetRequest TOTAL: {count}') if count >= 1: # if user from this wallet address has requested >= 1 times in the past week, proceed = False # stop progress for the program ctx['message'] = 'Already requested within the past week' # display error message ctx['error'] = True else: # else save the request to the database iprequest_item.save() faucetrequest_item.save() step += 1 logger_request.info('\n----------------------------') logger_request.info(f'IP Address: {client}') logger_request.info(f'Wallet Address: {address_to}') logger_request.info(f'Date of Request: {now}') logger_request.info('----------------------------\n') elif (proceed is True) & (step == 1): ctx['error'] = True ctx['message'] = 'Request failed. Please try again.' if (proceed is True) & (step == 2): # make transaction tx = self._make_tx( Helper.AddrStrToScriptHash( address=address_to).ToArray()) # neo-faucet used ContractTransaction() for this part because they created a smart contract to # transfer the system assets. # Since this is a nep5 token faucet, we use InvocationTransaction() to invoke the token's # 'transfer' function. if type(tx) is InvocationTransaction: logger.info('ALL OK!!!') step = 0 self.sent_tx = tx # sets the objects instance variable sent.tx to the tx info request.redirect( '/success') # redirects to the success page return succeed(None) else: ctx['message'] = f'Error constructing transaction: {tx}' ctx['error'] = True else: if ctx['message'] is None: ctx['message'] = 'You must input a wallet address to proceed' ctx['error'] = True except Exception as e: logger_request.error(f'exception: {e}') ctx['error'] = True ctx['message'] = f'Could not process request: {e}' output = self.j2_env.get_template('index.html').render(ctx) return output @app.route('/success') def app_success(self, request): """ :param request: klein request object that gets passed into every route function :returns: param request: """ ctx = self._get_context() if not self.sent_tx: # error checking logger.info('NO SENT TX:') request.redirect('/') return succeed(None) senttx_json = json.dumps(self.sent_tx.ToJson(), indent=4) ctx['tx_json'] = senttx_json ctx['message'] = f'Your request has been relayed to the network. Transaction: {self.sent_tx.Hash.ToString()}' output = self.j2_env.get_template('success.html').render(ctx) self.sent_tx = None # resets the instance variable self.wallet.Rebuild() # update wallet self.wallet._current_height = 100000 # still don't understand why this block height is so special return output