pybitcash/bitcash

#ConnectionError: Transaction broadcast failed, or Unspents were already used.

Closed this issue · 10 comments

I've been playing around with Bitcash sending memos and I found a bug.
Once a Bitcash wallet is fund, I can send an OP_RETURN message with the following command:
key.send([], message = "foobar", leftover = key.address)

But if I try a second time I get this error:
ConnectionError: Transaction broadcast failed, or Unspents were already used.
Status code is 400: the tx is not well constructed.

The error get solved if a request my wallet balance: key.get_balance()
Once the get_balance() function is executed, bitcash provides a different raw transaction and it works again. Maybe after sending the unspent UTXOs are not upgraded?

Yes, I think you are right. You have to manually get unspents or get balance (which does the same thing) sending one and then another.

I don't think I'd want send to always get unspents because there's many cases where you send, program exits, run it again later, etc. Unless you can think of a good way to implement a fix that wouldn't slow down the common case of sending one transaction at a time.

I've been running into similar problems, but have not been able to get anything to work with key.get_balance() nor key.get_unspents(). Could you please expand on what's going on, and the fix should work? The first transaction that worked: link
Code:

key = wif_to_key(PRIVATE_KEY)
tx = [(key.address, 1, 'satoshi')]
my_balance = int(key.get_balance())
print("Address: %s" % key.address)
print("This address has a balance of: %d satoshi or %f BCH" % (my_balance, my_balance/100000000.0))
key.get_unspents()
# Format message according to memo.cash protocol
text = 'hello'
encoded_text = bytearray([int("6d",16),int("02",16),len(text)])
encoded_text.extend(map(ord, text))
print(encoded_text)
key.send(tx, message=encoded_text)

@Gz75y45ms3kc what wasn't working, exactly? It said unspents were already used? Is this the first time you run the script or the second? Are you waiting for confirmations?

That TX is a couple of months old.

@teran-mckinney Hi Teran, now I know it I will just take in mind. I find the get_balance() function pretty fast, so I don't think it will slow down sending tx a lot.
@Gz75y45ms3kc Your code gives a 400 error. Instead of sending 1 sat to your own address thus making additional outputs, just use this:

key = wif_to_key(PRIVATE_KEY)
my_balance = int(key.get_balance())
print("Address: %s" % key.address)
print("This address has a balance of: %d satoshi or %f BCH" % (my_balance, my_balance/100000000.0))
key.get_unspents()
text = 'hello'
encoded_text = bytearray([int("6d",16),int("02",16),len(text)])
encoded_text.extend(map(ord, text))
print(encoded_text)
key.send([], message=encoded_text, leftover = key.address)

Taking a look at your tx, you will see that this code does not follow the memo protocol: https://memo.cash/explore/tx/ffbcb7d441dad6f829754c681d41e70721082d5d5e4930c03859bd913c59a925

In hex, a correct memo protocol have this data:
6a -> OP_RETURN, this is done by Bitcash
02 -> push 2 bytes
6d02 -> the pushed data, means "memo post"
Then, depending on the message size, 1 bytes that could be OP_PUSHDATA1, OP_PUSHDATA2 or OP_PUSHDATA4. Next, the message size in hex and finally, the message.
I must confess that I don't know exactly how to work with hex, bytes, strings... It's a mess for me.
This code generates the correct hex data, but you cannot provide Bitcash with a raw hex message :(
Any tips?

from bitcash.wallet import Key
from bitcash.transaction import get_op_pushdata_code
from bitcash.utils import bytes_to_hex

def send_memo(key, message):
    POST_MEMO_PREFIX = "026d02"
    PUSHDATA_CODE = bytes_to_hex(get_op_pushdata_code(message))
    msg = bytes_to_hex(message.encode('utf-8'))
    key.send([], message = POST_MEMO_PREFIX + PUSHDATA_CODE + msg, leftover = key.address)
    key.get_balance()
    return Done

EDIT: Sorry, I don't know how to embed code in Github.

Understandable, but sometimes the API may succeed getting unspents and broadcasting, then fail getting unspents after. So should send() throw an exception, a warning, or suceed? I guess I'm being pedantic. I'm used to seeing a lot of the various APIs go pretty slowly so I like to interact with them as little as possible.

You can wrap your code with three backticks (`). So using single quotes, instead of backticks:

'''
My code here.
'''

Or: 'single line of code'

https://github.github.com/gfm/

https://learnxinyminutes.com/docs/markdown/

Thanks for looking into @Gz75y45ms3kc's issue.

Hi @teran-mckinney now I know how to embed code. I understand your argument, Bitcash already works well enough.

@Gz75y45ms3kc About sending memos, I think it's a interesting use case for many dApps developers.
Here is my approach, it works fine but the API throws a 400 error if there's some strange character like "ñ".

from bitcash.wallet import Key
from bitcash.transaction import get_op_pushdata_code
from bitcash.utils import bytes_to_hex, hex_to_bytes
from bitcash.network.services import NetworkAPI

def send_memo(key, message):
    POST_MEMO_PREFIX = "026d02"
    PUSHDATA_CODE = bytes_to_hex(get_op_pushdata_code(message))
    encoded_message = hex_to_bytes(POST_MEMO_PREFIX + PUSHDATA_CODE + bytes_to_hex(message.encode('utf-8')))
    if len(encoded_message) <= 220:
        memo_tx = key.create_transaction([], message = encoded_message, leftover = key.address, custom_pushdata = True)
        API_RESULT = NetworkAPI.broadcast_tx(memo_tx)
        key.get_balance()
    else:
        return "Error: message longer than 220 bytes"
    
    if API_RESULT == 200:
        return "Memo sent"
    else:
        return "Sorry, something went wrong. API returned this: " + API_RESULT

Thank you @teran-mckinney and @libercash for your help with this. The code is working now and is following the memo cash protocol: link
Even though the transactions are going through, the code is saying something went wrong:

    return "Sorry, something went wrong. API returned this: " + API_RESULT
TypeError: can only concatenate str (not "NoneType") to str

Is this something I should be worried about?
Can either of you suggest reading material to help me under what's going on. I've looked at Mastering Bitcoin, but that doesn't seem to go into enough details (and is BTC not BCH). This page was a little more helpful, but still not giving the full picture. Is browsing the code itself the best way to understand bitcash?

@Gz75y45ms3kc Hi! It seems that broadcast_tx function returns None. As the API output is printed, this simplified code is enough:

from bitcash.wallet import Key
from bitcash.transaction import get_op_pushdata_code
from bitcash.utils import bytes_to_hex, hex_to_bytes
from bitcash.network.services import NetworkAPI

def send_memo(key, message):
    POST_MEMO_PREFIX = "026d02"
    PUSHDATA_CODE = bytes_to_hex(get_op_pushdata_code(message))
    encoded_message = hex_to_bytes(POST_MEMO_PREFIX + PUSHDATA_CODE + bytes_to_hex(message.encode('utf-8')))
    if len(encoded_message) <= 220:
        memo_tx = key.create_transaction([], message = encoded_message, leftover = key.address, custom_pushdata = True)
        NetworkAPI.broadcast_tx(memo_tx)
        key.get_balance()
        return True
    else:
        return "Error: message longer than 220 bytes"

The ideal behaviour for broadcast_tx could be to return the TX ID, as send() does. Anyway, it works so you shouldn't be worried about it.
I don't know very much about programming and Bitcoin script, the only way to learn is to read Bitcash docs, the code itself and asking. Ask in memo if you have specific questions about memo.
Then try&error until you get what are you looking for. Tell me what you need, maybe I can help.
I have plenty free time, if I can I'll implement basic SLP functionality on Bitcash.

@libercash The transaction ID can be worked out from the raw transaction (switched to the testnet for testing):

from bitcash.wallet import wif_to_key, PrivateKeyTestnet
from bitcash.transaction import get_op_pushdata_code, calc_txid
from bitcash.utils import bytes_to_hex, hex_to_bytes
from bitcash.network.services import NetworkAPI

def send_memo(key, message):
	POST_MEMO_PREFIX = "026d02"
	PUSHDATA_CODE = bytes_to_hex(get_op_pushdata_code(message))
	encoded_message = hex_to_bytes(POST_MEMO_PREFIX + PUSHDATA_CODE + bytes_to_hex(message.encode('utf-8')))
	# print(encoded_message)
	# print("Length of encoded message: %s" % len(encoded_message))
	if len(encoded_message) <= 220:
		memo_tx = key.create_transaction([], message = encoded_message, leftover = key.address, custom_pushdata = True)
		try:
			NetworkAPI.broadcast_tx_testnet(memo_tx)
		except Error as e:
			print("Error Exception!", e.strerror)
			exit(1)
		key.get_balance()
		return(calc_txid(memo_tx))
	else:
		print("Error: message longer than 220 bytes.")
		exit(1)

I think I'm getting the hang of it now, and this is enough for what I need. Thanks again for your help!

Blockdozer is giving me a 503 server error: link

And since Blockdozer is needed for get unspents, the actual posting of the transactions doesn't (reliably) work either. I have discovered there is now a testnet API on bitcoin.com: link