Skip to content

Instantly share code, notes, and snippets.

@thechiaplot
Created November 3, 2021 16:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thechiaplot/ba2c903fca9926348f05c98e95eaeab1 to your computer and use it in GitHub Desktop.
Save thechiaplot/ba2c903fca9926348f05c98e95eaeab1 to your computer and use it in GitHub Desktop.
TxMojos.py
import asyncio
import os
import sys
import sqlite3
import pathlib
import random
from chia.consensus.default_constants import DEFAULT_CONSTANTS as constants
from blspy import AugSchemeMPL
from chia.types.coin_spend import CoinSpend
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_by_opcode, conditions_for_solution
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program, SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.hash import std_hash
from chia.util.keychain import Keychain
from chia.wallet.derive_keys import master_sk_to_wallet_sk
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
DEFAULT_HIDDEN_PUZZLE_HASH,
calculate_synthetic_secret_key,
puzzle_for_pk,
)
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_info import WalletInfo
from chia.rpc.full_node_rpc_client import FullNodeRpcClient
from chia.util.ints import uint16
def create_coin_spend(w, private_key, spend_coin, spend_coin_ph_index, dest_ph, fee=0):
private_key = master_sk_to_wallet_sk(private_key, spend_coin_ph_index)
pubkey = private_key.get_g1()
if fee == 0:
primaries = [{
'puzzlehash': dest_ph,
'amount': 1,
}]
else:
primaries = []
message_list = [spend_coin.name()]
for i in primaries:
message_list.append(
Coin(spend_coin.name(), i['puzzlehash'], i['amount']).name()
)
message: bytes32 = std_hash(b"".join(message_list))
puzzle: Program = puzzle_for_pk(bytes(pubkey))
solution: Program = w.make_solution(
primaries=primaries,
coin_announcements={message},
fee=fee,
)
coin_spend = CoinSpend(
spend_coin,
SerializedProgram.from_bytes(bytes(puzzle)),
SerializedProgram.from_bytes(bytes(solution)),
)
err, con, cost = conditions_for_solution(
coin_spend.puzzle_reveal, coin_spend.solution, constants.MAX_BLOCK_COST_CLVM
)
if not con:
raise ValueError(err)
conditions_dict = conditions_by_opcode(con)
synthetic_secret_key = calculate_synthetic_secret_key(
private_key, DEFAULT_HIDDEN_PUZZLE_HASH
)
signatures = []
for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_UNSAFE, []):
msg = cwa.vars[1]
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)
for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_ME, []):
msg = cwa.vars[1] + bytes(coin_spend.coin.name()) + constants.AGG_SIG_ME_ADDITIONAL_DATA
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)
return coin_spend, signatures
async def new_coin_records_loop(node_rpc_client, coin_records, coin_records_event, phs):
start = 0
phs = list(phs.keys())
while True:
state = await node_rpc_client.get_blockchain_state()
end = state['peak'].height
new_records = [i for i in await node_rpc_client.get_coin_records_by_puzzle_hashes(
phs,
include_spent_coins=False,
start_height=start,
end_height=end,
) if int(i.coin.amount) == 1]
print(f'Found {len(new_records)} new')
coin_records += new_records
coin_records_event.set()
start = end
await asyncio.sleep(20)
async def main():
node_hostname = os.environ.get('NODE_HOSTNAME', 'node')
node_ssl_path = os.environ.get('NODE_SSL_PATH', '/data/node_ssl')
node_rpc_client = await FullNodeRpcClient.create(
node_hostname, uint16(int(os.environ.get('NODE_PORT', 8555))), pathlib.Path(''), {
'private_ssl_ca': {
'crt': f'{node_ssl_path}/ca/private_ca.crt',
'key': f'{node_ssl_path}/ca/private_ca.key',
},
'daemon_ssl': {
'private_crt': f'{node_ssl_path}/daemon/private_daemon.crt',
'private_key': f'{node_ssl_path}/daemon/private_daemon.key',
},
}
)
s = sqlite3.connect(os.environ['LOCAL_DB_PATH'])
cursor = s.cursor()
cursor.execute('select puzzle_hash, derivation_index from derivation_paths order by derivation_index LIMIT 1300')
phs = {bytes.fromhex(i[0]): i[1] for i in cursor.fetchall()[1:]}
cursor.close()
s.close()
coin_records = []
coin_records_event = asyncio.Event()
asyncio.create_task(new_coin_records_loop(node_rpc_client, coin_records, coin_records_event, phs))
fee = int(os.environ['FEE'])
keychain: Keychain = Keychain()
private_key = keychain.get_all_private_keys()[0][0]
# Hack to use Wallet implementation of make_solution
wi = WalletInfo(0, '', 0, '')
w = await Wallet.create(None, wi)
# Wait to get the first round of coin records
await coin_records_event.wait()
phs_list = list(phs.keys())
while True:
num_crs = len(coin_records)
if num_crs == 0:
print("No CRs, waiting")
await asyncio.sleep(10)
continue
num_per_tx = min(num_crs, random.randint(1, 100))
spends = []
signatures = []
coins_to_spend = coin_records[:num_per_tx]
for idx, coin_to_spend in enumerate(coins_to_spend):
coin_records.remove(coin_to_spend)
ph_dest = phs_list[num_per_tx + idx]
spend_coin, sigs = create_coin_spend(w, private_key, coin_to_spend.coin, phs[coin_to_spend.coin.puzzle_hash], ph_dest)
spends.append(spend_coin)
signatures += sigs
if fee > 0 and coin_records:
coin_to_spend = coin_records.pop()
ph_dest = phs_list[0]
spend_coin, sigs = create_coin_spend(w, private_key, coin_to_spend.coin, phs[coin_to_spend.coin.puzzle_hash], ph_dest, fee)
spends.append(spend_coin)
signatures += sigs
aggsig = AugSchemeMPL.aggregate(signatures)
sb = SpendBundle(spends, aggsig)
await node_rpc_client.push_tx(sb)
print(f'{len(coins_to_spend)}.', end='')
sys.stdout.flush()
node_rpc_client.close()
await node_rpc_client.await_closed()
if __name__ == '__main__':
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment