diff --git a/OP_RETURN.py b/OP_RETURN.py index 594bc8d..5347607 100644 --- a/OP_RETURN.py +++ b/OP_RETURN.py @@ -32,21 +32,21 @@ basestring except NameError: basestring = str - - + + # User-defined quasi-constants OP_RETURN_BITCOIN_IP='127.0.0.1' # IP address of your bitcoin node -OP_RETURN_BITCOIN_USE_CMD=False # use command-line instead of JSON-RPC? +OP_RETURN_BITCOIN_USE_CMD=True # use command-line instead of JSON-RPC? if OP_RETURN_BITCOIN_USE_CMD: - OP_RETURN_BITCOIN_PATH='/usr/bin/bitcoin-cli' # path to bitcoin-cli executable on this server - + OP_RETURN_BITCOIN_PATH='/usr/bin/groestlcoin-cli' # path to groestlcoin-cli executable on this server + else: OP_RETURN_BITCOIN_PORT='' # leave empty to use default port for mainnet/testnet - OP_RETURN_BITCOIN_USER='' # leave empty to read from ~/.bitcoin/bitcoin.conf (Unix only) - OP_RETURN_BITCOIN_PASSWORD='' # leave empty to read from ~/.bitcoin/bitcoin.conf (Unix only) - + OP_RETURN_BITCOIN_USER='' # leave empty to read from ~/.groestlcoin/groestlcoin.conf (Unix only) + OP_RETURN_BITCOIN_PASSWORD='' # leave empty to read from ~/.groestlcoin/groestlcoin.conf (Unix only) + OP_RETURN_BTC_FEE=0.0001 # BTC fee to pay per transaction OP_RETURN_BTC_DUST=0.00001 # omit BTC outputs smaller than this @@ -55,50 +55,51 @@ OP_RETURN_NET_TIMEOUT=10 # how long to time out (in seconds) when communicating with bitcoin node +SAT = 100000000 # coin divisible by how many satoshis # User-facing functions def OP_RETURN_send(send_address, send_amount, metadata, testnet=False): # Validate some parameters - + if not OP_RETURN_bitcoin_check(testnet): - return {'error': 'Please check Bitcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} + return {'error': 'Please check Groestlcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} result=OP_RETURN_bitcoin_cmd('validateaddress', testnet, send_address) if not ('isvalid' in result and result['isvalid']): return {'error': 'Send address could not be validated: '+send_address} - + if isinstance(metadata, basestring): metadata=metadata.encode('utf-8') # convert to binary string metadata_len=len(metadata) - + if metadata_len>65536: return {'error': 'This library only supports metadata up to 65536 bytes in size'} - + if metadata_len>OP_RETURN_MAX_BYTES: return {'error': 'Metadata has '+str(metadata_len)+' bytes but is limited to '+str(OP_RETURN_MAX_BYTES)+' (see OP_RETURN_MAX_BYTES)'} - + # Calculate amounts and choose inputs output_amount=send_amount+OP_RETURN_BTC_FEE inputs_spend=OP_RETURN_select_inputs(output_amount, testnet) - + if 'error' in inputs_spend: return {'error': inputs_spend['error']} - + change_amount=inputs_spend['total']-output_amount # Build the raw transaction - + change_address=OP_RETURN_bitcoin_cmd('getrawchangeaddress', testnet) - + outputs={send_address: send_amount} - + if change_amount>=OP_RETURN_BTC_DUST: outputs[change_address]=change_amount - + raw_txn=OP_RETURN_create_txn(inputs_spend['inputs'], outputs, metadata, len(outputs), testnet) # Sign and send the transaction, return result @@ -114,25 +115,25 @@ def OP_RETURN_store(data, testnet=False): # Validate parameters and get change address if not OP_RETURN_bitcoin_check(testnet): - return {'error': 'Please check Bitcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} - + return {'error': 'Please check Groestlcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} + if isinstance(data, basestring): data=data.encode('utf-8') # convert to binary string - + data_len=len(data) if data_len==0: return {'error': 'Some data is required to be stored'} change_address=OP_RETURN_bitcoin_cmd('getrawchangeaddress', testnet) - + # Calculate amounts and choose first inputs to use output_amount=OP_RETURN_BTC_FEE*int((data_len+OP_RETURN_MAX_BYTES-1)/OP_RETURN_MAX_BYTES) # number of transactions required - + inputs_spend=OP_RETURN_select_inputs(output_amount, testnet) if 'error' in inputs_spend: return {'error': inputs_spend['error']} - + inputs=inputs_spend['inputs'] input_amount=inputs_spend['total'] @@ -147,32 +148,34 @@ def OP_RETURN_store(data, testnet=False): for data_ptr in range(0, data_len, OP_RETURN_MAX_BYTES): # Some preparation for this iteration - + last_txn=((data_ptr+OP_RETURN_MAX_BYTES)>=data_len) # is this the last tx in the chain? - change_amount=input_amount-OP_RETURN_BTC_FEE + change_amount = ( + int(input_amount * SAT) - int(OP_RETURN_BTC_FEE * SAT) + ) / SAT metadata=data[data_ptr:data_ptr+OP_RETURN_MAX_BYTES] - + # Build and send this transaction - + outputs={} if change_amount>=OP_RETURN_BTC_DUST: # might be skipped for last transaction outputs[change_address]=change_amount - + raw_txn=OP_RETURN_create_txn(inputs, outputs, metadata, len(outputs) if last_txn else 0, testnet) - + send_result=OP_RETURN_sign_send_txn(raw_txn, testnet) - + # Check for errors and collect the txid - + if 'error' in send_result: result['error']=send_result['error'] break - + result['txids'].append(send_result['txid']) - + if data_ptr==0: result['ref']=OP_RETURN_calc_ref(height, send_result['txid'], avoid_txids) - + # Prepare inputs for next iteration inputs=[{ @@ -181,7 +184,7 @@ def OP_RETURN_store(data, testnet=False): }] input_amount=change_amount - + # Return the final result return result @@ -191,18 +194,18 @@ def OP_RETURN_retrieve(ref, max_results=1, testnet=False): # Validate parameters and get status of Bitcoin Core if not OP_RETURN_bitcoin_check(testnet): - return {'error': 'Please check Bitcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} - + return {'error': 'Please check groestlcoin Core is running and OP_RETURN_BITCOIN_* constants are set correctly'} + max_height=int(OP_RETURN_bitcoin_cmd('getblockcount', testnet)) heights=OP_RETURN_get_ref_heights(ref, max_height) - + if not isinstance(heights, list): return {'error': 'Ref is not valid'} # Collect and return the results - + results=[] - + for height in heights: if height==0: txids=OP_RETURN_list_mempool_txns(testnet) # if mempool, only get list for now (to save RPC calls) @@ -210,52 +213,52 @@ def OP_RETURN_retrieve(ref, max_results=1, testnet=False): else: txns=OP_RETURN_get_block_txns(height, testnet) # if block, get all fully unpacked txids=txns.keys() - + for txid in txids: if OP_RETURN_match_ref_txid(ref, txid): if height==0: txn_unpacked=OP_RETURN_get_mempool_txn(txid, testnet) else: txn_unpacked=txns[txid] - + found=OP_RETURN_find_txn_data(txn_unpacked) - - if found: + + if found: # Collect data from txid which matches ref and contains an OP_RETURN - + result={ 'txids': [str(txid)], 'data': found['op_return'], } - + key_heights={height: True} - + # Work out which other block heights / mempool we should try - + if height==0: try_heights=[] # nowhere else to look if first still in mempool else: result['ref']=OP_RETURN_calc_ref(height, txid, txns.keys()) try_heights=OP_RETURN_get_try_heights(height+1, max_height, False) - + # Collect the rest of the data, if appropriate - + if height==0: this_txns=OP_RETURN_get_mempool_txns(testnet) # now retrieve all to follow chain else: this_txns=txns - + last_txid=txid this_height=height - + while found['index'] < (len(txn_unpacked['vout'])-1): # this means more data to come next_txid=OP_RETURN_find_spent_txid(this_txns, last_txid, found['index']+1) - + # If we found the next txid in the data chain - + if next_txid: result['txids'].append(str(next_txid)) - + txn_unpacked=this_txns[next_txid] found=OP_RETURN_find_txn_data(txn_unpacked) @@ -265,11 +268,11 @@ def OP_RETURN_retrieve(ref, max_results=1, testnet=False): else: result['error']='Data incomplete - missing OP_RETURN' break - + last_txid=next_txid - + # Otherwise move on to the next height to keep looking - + else: if len(try_heights): this_height=try_heights.pop(0) @@ -277,20 +280,20 @@ def OP_RETURN_retrieve(ref, max_results=1, testnet=False): if this_height==0: this_txns=OP_RETURN_get_mempool_txns(testnet) else: - this_txns=OP_RETURN_get_block_txns(this_height, testnet) + this_txns=OP_RETURN_get_block_txns(this_height, testnet) else: result['error']='Data incomplete - could not find next transaction' break - - # Finish up the information about this result - - result['heights']=list(key_heights.keys()) + + # Finish up the information about this result + + result['heights']=list(key_heights.keys()) results.append(result) - + if len(results)>=max_results: break # stop if we have collected enough - + return results @@ -299,27 +302,27 @@ def OP_RETURN_retrieve(ref, max_results=1, testnet=False): def OP_RETURN_select_inputs(total_amount, testnet): # List and sort unspent inputs by priority - unspent_inputs=OP_RETURN_bitcoin_cmd('listunspent', testnet, 0) + unspent_inputs=OP_RETURN_bitcoin_cmd('listunspent', testnet, 0) if not isinstance(unspent_inputs, list): return {'error': 'Could not retrieve list of unspent inputs'} - + unspent_inputs.sort(key=lambda unspent_input: unspent_input['amount']*unspent_input['confirmations'], reverse=True) - + # Identify which inputs should be spent inputs_spend=[] input_amount=0 - + for unspent_input in unspent_inputs: inputs_spend.append(unspent_input) input_amount+=unspent_input['amount'] if input_amount>=total_amount: break # stop when we have enough - + if input_amount=4: txid_binary=OP_RETURN_hex_to_bin(parts[1][0:4]) parts[1]=ord(txid_binary[0:1])+256*ord(txid_binary[1:2])+65536*0 else: return None - + parts=list(map(int, parts)) - + if parts[1]>983039: # 14*65536+65535 return None - + return parts @@ -548,18 +551,18 @@ def OP_RETURN_get_ref_heights(ref, max_height): parts=OP_RETURN_get_ref_parts(ref) if not parts: return None - + return OP_RETURN_get_try_heights(parts[0], max_height, True) def OP_RETURN_get_try_heights(est_height, max_height, also_back): forward_height=est_height back_height=min(forward_height-1, max_height) - + heights=[] mempool=False try_height=0 - + while True: if also_back and ((try_height%3)==2): # step back every 3 tries heights.append(back_height) @@ -570,20 +573,20 @@ def OP_RETURN_get_try_heights(est_height, max_height, also_back): if not mempool: heights.append(0) # indicates to try mempool mempool=True - + elif not also_back: break # nothing more to do here - + else: heights.append(forward_height) - + forward_height+=1 - + if len(heights)>=OP_RETURN_MAX_BLOCKS: break - + try_height+=1 - + return heights @@ -601,12 +604,12 @@ def OP_RETURN_match_ref_txid(ref, txid): return txid_part==txid_match # exact binary comparison -# Unpacking and packing bitcoin blocks and transactions +# Unpacking and packing bitcoin blocks and transactions def OP_RETURN_unpack_block(binary): buffer=OP_RETURN_buffer(binary) block={} - + block['version']=buffer.shift_unpack(4, '100000: # sanity check return None - + for _ in range(inputs): input={} - + input['txid']=OP_RETURN_bin_to_hex(buffer.shift(32)[::-1]) input['vout']=buffer.shift_unpack(4, '100000: # sanity check return None - + for _ in range(outputs): output={} - + output['value']=float(buffer.shift_uint64())/100000000 length=buffer.shift_varint() output['scriptPubKey']=OP_RETURN_bin_to_hex(buffer.shift(length)) - + txn['vout'].append(output) - + + if segwit: + for _ in range(inputs): + witnesses=buffer.shift_varint() + for _ in range(witnesses): + length=buffer.shift_varint() + buffer.shift(length) # skip + txn['locktime']=buffer.shift_unpack(4, ' - is the bitcoin address of the recipient - is the amount to send (in units of BTC) + is the groestlcoin address of the recipient + is the amount to send (in units of GRS) is a hex string or raw string containing the OP_RETURN metadata (auto-detection: treated as a hex string if it is a valid one) - should be 1 to use the bitcoin testnet, otherwise it can be omitted + should be 1 to use the groestlcoin testnet, otherwise it can be omitted * Outputs an error if one occurred or the txid if sending was successful -* Wait a few seconds then check http://coinsecrets.org/ for your OP_RETURN transaction. +* Wait a few seconds then check blockexplorer for your OP_RETURN transaction. * Examples: - python send-OP_RETURN.py 149wHUMa41Xm2jnZtqgRx94uGbZD9kPXnS 0.001 'Hello, blockchain!' - python send-OP_RETURN.py 149wHUMa41Xm2jnZtqgRx94uGbZD9kPXnS 0.001 48656c6c6f2c20626c6f636b636861696e21 - python send-OP_RETURN.py mzEJxCrdva57shpv62udriBBgMECmaPce4 0.001 'Hello, testnet!' 1 + python send-OP_RETURN.py FqLDjQPjguc5SHwM2RxMbX24fsc8WmQoBA 0.001 'Hello, blockchain!' + python send-OP_RETURN.py FqLDjQPjguc5SHwM2RxMbX24fsc8WmQoBA 0.001 48656c6c6f2c20626c6f636b636861696e21 + python send-OP_RETURN.py muCVFTRDC6JVHuYx3qupeQ6hraoM8ENGUy 0.001 'Hello, testnet!' 1 As a library: * OP_RETURN_send(send_address, send_amount, metadata, testnet=False) - send_address is the bitcoin address of the recipient - send_amount is the amount to send (in units of BTC) + send_address is the groestlcoin address of the recipient + send_amount is the amount to send (in units of GRS) metadata is a string of raw bytes containing the OP_RETURN metadata - testnet is whether to use the bitcoin testnet network (False if omitted) + testnet is whether to use the groestlcoin testnet network (False if omitted) * Returns: {'error': ''} or: {'txid': ''} * Examples - OP_RETURN_send('149wHUMa41Xm2jnZtqgRx94uGbZD9kPXnS', 0.001, 'Hello, blockchain!') - OP_RETURN_send('mzEJxCrdva57shpv62udriBBgMECmaPce4', 0.001, 'Hello, testnet!', True) + OP_RETURN_send('FqLDjQPjguc5SHwM2RxMbX24fsc8WmQoBA', 0.001, 'Hello, blockchain!') + OP_RETURN_send('muCVFTRDC6JVHuYx3qupeQ6hraoM8ENGUy', 0.001, 'Hello, testnet!', True) @@ -72,30 +70,30 @@ On the command line: is a hex string or raw string containing the data to be stored (auto-detection: treated as a hex string if it is a valid one) - should be 1 to use the bitcoin testnet, otherwise it can be omitted + should be 1 to use the groestlcoin testnet, otherwise it can be omitted * Outputs an error if one occurred or if successful, the txids that were used to store the data and a short reference that can be used to retrieve it using this library. -* Wait a few seconds then check http://coinsecrets.org/ for your OP_RETURN transactions. +* Wait a few seconds then check blockexplorer for your OP_RETURN transactions. * Examples: python store-OP_RETURN.py 'This example stores 47 bytes in the blockchain.' python store-OP_RETURN.py 'This example stores 44 bytes in the testnet.' 1 - - + + As a library: * OP_RETURN_store(data, testnet=False) data is the string of raw bytes to be stored - testnet is whether to use the bitcoin testnet network (False if omitted) - + testnet is whether to use the groestlcoin testnet network (False if omitted) + * Returns: {'error': ''} or: {'txids': ['<1st txid>', '<2nd txid>', ...], 'ref': ''} - + * Examples: OP_RETURN_store('This example stores 47 bytes in the blockchain.') @@ -111,26 +109,26 @@ On the command line: * python retrieve-OP_RETURN.py is the reference that was returned by a previous storage operation - should be 1 to use the bitcoin testnet, otherwise it can be omitted - + should be 1 to use the groestlcoin testnet, otherwise it can be omitted + * Outputs an error if one occurred or if successful, the retrieved data in hexadecimal and ASCII format, a list of the txids used to store the data, a list of the blocks in which the data is stored, and (if available) the best ref for retrieving the data quickly in future. This may or may not be different from the ref you provided. - + * Examples: python retrieve-OP_RETURN.py 356115-052075 python retrieve-OP_RETURN.py 396381-059737 1 - - + + As a library: * OP_RETURN_retrieve(ref, max_results=1, testnet=False) ref is the reference that was returned by a previous storage operation max_results is the maximum number of results to retrieve (in general, omit for 1) - testnet is whether to use the bitcoin testnet network (False if omitted) + testnet is whether to use the groestlcoin testnet network (False if omitted) * Returns: {'error': ''} or: {'data': '', @@ -138,18 +136,18 @@ As a library: 'heights': [, , ...], 'ref': '', 'error': ''} - - A value of 0 in the 'heights' array means some data is still in the mempool. + + A value of 0 in the 'heights' array means some data is still in the mempool. The 'ref' and 'error' elements are only present if appropriate. - + * Examples: OP_RETURN_retrieve('356115-052075') OP_RETURN_retrieve('396381-059737', 1, True) - - + + VERSION HISTORY --------------- v2.0.2 - 30 June 2015 -* First port of php-OP_RETURN to Python \ No newline at end of file +* First port of php-OP_RETURN to Python diff --git a/send-OP_RETURN.py b/send-OP_RETURN.py index 869d6f8..82a7b95 100644 --- a/send-OP_RETURN.py +++ b/send-OP_RETURN.py @@ -1,19 +1,19 @@ # send-OP_RETURN.py -# +# # CLI wrapper for OP_RETURN.py to send bitcoin with OP_RETURN metadata # # Copyright (c) Coin Sciences Ltd -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -33,9 +33,9 @@ python send-OP_RETURN.py Examples: -python send-OP_RETURN.py 149wHUMa41Xm2jnZtqgRx94uGbZD9kPXnS 0.001 'Hello, blockchain!' -python send-OP_RETURN.py 149wHUMa41Xm2jnZtqgRx94uGbZD9kPXnS 0.001 48656c6c6f2c20626c6f636b636861696e21 -python send-OP_RETURN.py mzEJxCrdva57shpv62udriBBgMECmaPce4 0.001 'Hello, testnet blockchain!' 1''' +python send-OP_RETURN.py FqLDjQPjguc5SHwM2RxMbX24fsc8WmQoBA 0.001 'Hello, blockchain!' +python send-OP_RETURN.py FqLDjQPjguc5SHwM2RxMbX24fsc8WmQoBA 0.001 48656c6c6f2c20626c6f636b636861696e21 +python send-OP_RETURN.py muCVFTRDC6JVHuYx3qupeQ6hraoM8ENGUy 0.001 'Hello, testnet blockchain!' 1''' ) dummy, send_address, send_amount, metadata = sys.argv[0:4] @@ -53,5 +53,5 @@ if 'error' in result: print('Error: '+result['error']) else: - print('TxID: '+result['txid']+'\nWait a few seconds then check on: http://'+ - ('testnet.' if testnet else '')+'coinsecrets.org/') + print('TxID: '+result['txid']+'\nWait a few seconds then check on: https://'+ + ('testnet.' if testnet else '')+'groestlsight.groestlcoin.org/tx/'+result['txid']) diff --git a/store-OP_RETURN.py b/store-OP_RETURN.py index 03cd7ea..ed88dab 100644 --- a/store-OP_RETURN.py +++ b/store-OP_RETURN.py @@ -1,19 +1,19 @@ # store-OP_RETURN.py -# +# # CLI wrapper for OP_RETURN.py to store data using OP_RETURNs # # Copyright (c) Coin Sciences Ltd -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -32,7 +32,7 @@ '''Usage: python store-OP_RETURN.py ''' ) - + data=sys.argv[1] if len(sys.argv)>2: @@ -43,11 +43,13 @@ data_from_hex=OP_RETURN_hex_to_bin(data) if data_from_hex is not None: data=data_from_hex +else: + data=data.decode('utf-8') result=OP_RETURN_store(data, testnet) if 'error' in result: print('Error: '+result['error']) else: - print("TxIDs:\n"+"\n".join(result['txids'])+"\n\nRef: "+result['ref']+"\n\nWait a few seconds then check on: http://"+ - ('testnet.' if testnet else '')+'coinsecrets.org/') + print("TxIDs:\n"+"\n".join(result['txids'])+"\n\nRef: "+result['ref']+"\n\nWait a few seconds then check on: https://"+ + ('testnet.' if testnet else '')+'groestlsight.groestlcoin.org/tx/'+result['txid'])