beem is a python library for steem. The newest version is 0.19.21. There are now 363 unit tests with a coverage of 84%. Running all tests takes about one hour.
I created a discord channel for answering question or discussing beem: https://discord.gg/4HM592V
I requested a logo for beem, you can participate and submit here until 13 April 23:59 UTC.
Thanks to the node issues, exception handling could be greatly improved. The error count is now indviduall for each node.
Every time an error was observed, the error count for the specific node is increased and the next node from the list is used. There is num_retries which is normally deactivated (-1), but when set, NumRetriesReached is raised when
NumRetriesReached errors are observedx was used NumRetriesReached times and everytime an error occured. The other nodes are also faulty and therefore node x was tried NumRetriesReached times.num_retries_call is newly introduced and normally set to 5. When an RPC call was not successfully and the error means that it could work when retrying, error_cnt_call is increased and the same call is retried. Only when num_retries_call is reached, the next node is used. The error count for num_retries_call is resetted after each sucessfull RPC call.
Different kind of server errors are parsed now and handled.
rshares_to_vote_pct was added to the steem class. A vote with 1e9 rshares should be casted. The voter has 500 Steem Power and 93% voting power left. What is the needed vote percentage?
from beem import Steem
stm = Steem()
stm.rshares_to_vote_pct(1e9, steem_power=500, voting_power=9300)
537
The vote should be casted with 5.37%.
Thanks to the unit tests from @leprechaun, I could improve the Transactionbuilder in the newest version of beem.
A transaction is a JSON structure and consists of
expirationref_block_numref_block_prefixoperationsextensionssignaturesHere you can see an example for a transfer transaction:
{'expiration': '2018-04-09T15:35:21', 'ref_block_num': 12523,
'ref_block_prefix': 2784763404,
'operations': [['transfer', {'from': 'user_a', 'to': 'user_b', 'amount': '0.010 SBD',
'memo': 'hello'}]],
'extensions': [], 'signatures': []}
A transaction is build by the TransactionBuilder and can only be broadcast to the blockchain, when it is correctly signed. A transfer transaction must be signed by the active private key of the sender and broadcasted before expiration.
There is the possibility to add other accounts, that are able to or must sign. The transaction can then be transmitted when at least weight_threshold accounts from the list signed the transaction.
For example, the account of user user_a has for its active key one addional user user_c which must also sign all transaction, as the weight_threshold is 2.
account["active"] =
{'weight_threshold': 2, 'account_auths': [['user_c', 1]], 'key_auths': [['STM...', 1]]}
The functions:
appendSigneraddSigningInformationappendMissingSignaturescan only be used when private keys were stored in the wallet.
Whenever the keys should be set directly, only
appendWifmust be used.
When not using the wallet, the transaction has to build by hand.
from beem import Steem
from beem.transactionbuilder import TransactionBuilder
stm = Steem()
tx = TransactionBuilder(steem_instance=stm)
tx.appendOps(Transfer(**{"from": 'user_a',
"to": 'user_b',
"amount": '1.000 SBD',
"memo": 'test 2'}))
tx.appendWif('5.....') # `user_a`
tx.appendWif('5.....') # `user_c`
tx.sign()
tx.broadcast()
When user_a and user_c should not share their password, the last 4 lines become:
tx.appendWif('5.....') # `user_a`
tx.sign()
tx.clearWifs()
tx_json = tx.json()
# Send the tx_json structure to `user_c` and be sure that `tx_json["expiration"] is in the future.
tx = TransactionBuilder(tx=tx_json, steem_instance=stm)
tx.appendWif('5.....') # `user_c`
tx.sign(reconstruct_tx=False)
tx.broadcast()
tx.sign(reconstruct_tx=False) does not delete the signature from user_a.
When using the wallet, high level functions as beem.account.transfer
can be used. The keys can be stored permanent or temporary.
Let's assume the wallet is ready and a wallet-password is set. If not, a wallet can be started from zero with:
from beem import Steem
stm = Steem()
stm.wallet.wipe(True)
stm.wallet.create("secret_password")
In this case, we are taking the example from above, where user_a and user_c has to sign.
At the first time, the private keys must added to the wallet:
stm.wallet.unlock("secret_password")
stm.wallet.addPrivateKey("5............") # active private key from user_a
stm.wallet.addPrivateKey("5............") # active private key from user_c
Both private keys are stored temporary in the wallet, when they are given to the Steem class by:
from beem import Steem
stm = Steem(keys=["5............", "5............"]) # active keys from user_a and user_c
The keys are stored in stm.wallet.keys and can be deleted by stm.wallet.clear_local_keys().
With stm.wallet.setKeys('5....') new keys can be added to the temporary storage.
Now, we can start to broadcast a transaction (transfer 1 SBD from user_a to user_b with memo "test"):
from beem.account import Account
from beem import Steem
stm = Steem()
stm.wallet.unlock("secret_password")
acc = Account("user_a", steem_instance=stm)
acc.transfer("user_b", 1, "SBD", memo="test")
When the keys should be stored only temporary, it can be done by replacing:
stm = Steem()
stm.wallet.unlock("secret_password")
with
from beem import Steem
stm = Steem(keys=["5............", "5............"]) # keys from user_a and user_c
The transfer function automatically detects that the transaction has to be signed by user_a and user_c and searching for the keays in the wallet. When all were found, the transaction is signed and broadcasted.
from beem.transactionbuilder import TransactionBuilder
from beem import Steem
stm = Steem()
stm.wallet.unlock("secret_password")
tx = TransactionBuilder(steem_instance=stm)
tx.appendOps(Transfer(**{"from": 'user_a',
"to": 'user_b',
"amount": '1.000 SBD',
"memo": 'test 2'}))
tx.appendSigner('user_a', 'active')
tx.sign()
tx.broadcast()
It is detected that user_c is needed and automatically added. The private keys can also be stored temporary as in the example above.
user_astm.wallet contains now only the active key of user_a
from beem.transactionbuilder import TransactionBuilder
from beem import Steem
stm = Steem()
stm.wallet.unlock("secret_password")
tx = TransactionBuilder(steem_instance=stm)
tx.appendOps(Transfer(**{"from": 'user_a',
"to": 'user_b',
"amount": '1.000 SBD',
"memo": 'test 2'}))
tx.appendSigner('user_a', 'active')
tx.addSigningInformation("user_a", "active")
tx.sign()
tx.clearWifs()
tx_json = tx.json()
tx_json is now sended to user_c
user_cstm.wallet contains now only the active key of user_c
from beem.transactionbuilder import TransactionBuilder
from beem import Steem
stm = Steem()
stm.wallet.unlock("secret_password")
tx = TransactionBuilder(tx=tx_json, steem_instance=stm)
tx.appendMissingSignatures()
tx.sign(reconstruct_tx=False)
tx.broadcast()
user_astm.wallet is now empty.
from beem.transactionbuilder import TransactionBuilder
from beem import Steem
stm = Steem(keys=["5............"]) # keys from user_a
tx = TransactionBuilder(steem_instance=stm)
tx.appendOps(Transfer(**{"from": 'user_a',
"to": 'user_b',
"amount": '1.000 SBD',
"memo": 'test 2'}))
tx.appendSigner('user_a', 'active')
tx.addSigningInformation("user_a", "active")
tx.sign()
tx.clearWifs()
tx_json = tx.json()
tx_json is now sended to user_c
user_cstm.wallet contains now only the active key of user_c
from beem.transactionbuilder import TransactionBuilder
from beem import Steem
stm = Steem(keys=["5............"]) # keys from user_c
tx = TransactionBuilder(tx=tx_json, steem_instance=stm)
tx.appendMissingSignatures()
tx.sign(reconstruct_tx=False)
tx.broadcast()
I added a script which using pickle to serialize a block and then creates a hex number of it.
This line is then written as gzipped text file. One blockchain hour needs around 7 MB of disk space.
The advantage of this approach is that each line belongs to one block.
The example can be found here: https://github.com/holgern/beem/blob/master/examples/write_blocks_to_file.py
from binascii import hexlify, unhexlify
import gzip
from datetime import datetime, timedelta
from beem.blockchain import Blockchain
try:
from cPickle import dumps, loads
except ImportError:
from pickle import dumps, loads
def s_dump_binary(elt_to_pickle, file_obj):
pickled_elt_str = dumps(elt_to_pickle)
file_obj.write(hexlify(pickled_elt_str))
file_obj.write(bytes('\n'.encode("latin1")))
def s_load_binary(file_obj):
for line in file_obj:
elt = loads(unhexlify(line[:-1]))
yield elt
if __name__ == "__main__":
blockchain = Blockchain()
threading = True
thread_num = 8
cur_block = blockchain.get_current_block()
stop = cur_block.identifier
startdate = cur_block.time() - timedelta(seconds=3600)
start = blockchain.get_estimated_block_num(startdate, accurate=True)
outf = gzip.open('blocks1.pkl', 'w')
blocks = 0
for block in blockchain.stream(opNames=[], start=start, stop=stop, threading=threading, thread_num=thread_num):
s_dump_binary(block, outf)
The block data can be read by:
for block in s_load_binary(gzip.open('blocks1.pkl')):
print(block)
I added under examples a benchmark script benchmark_nodes2.py from which the results can be seen in this post: full-api-node-statistics-with-beem-april-10th-2018
test_vote improved