• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: Commit

Golang implemented sidechain for Bytom


Commit MetaInfo

Revision58a564d512221707f7ea6410f95ec75107dad1fd (tree)
Time2019-08-12 15:13:52
Authorwz <mars@byto...>
Commiterwz

Log Message

add merger utxo

Change Summary

Incremental Difference

--- a/README.md
+++ b/README.md
@@ -121,6 +121,11 @@ cast in consensus around, and choose how many rounds of consensus to allocate th
121121
122122 [Tool usage details](./cmd/votereward/README.md)
123123
124+
125+### Merger utxo
126+ UTXO has been merged to solve the problem that too much UTXO input causes a failed send transaction to fail.
127+ [details](./toolbar/merger_utxo_README.md)
128+
124129 ## License
125130
126131 [AGPL v3](./LICENSE)
--- /dev/null
+++ b/toolbar/merger_utxo/README.md
@@ -0,0 +1,93 @@
1+# UTXO merger
2+
3+
4+> **One last disclaimer:**
5+
6+**the code we are about to go over is in no way intended to be used as an example of a robust solution.**
7+
8+**We wouldn't be responsible for the consequences of using this tool.**
9+
10+**please check this python code carefully and use it later.**
11+
12+
13+Requirements: Python 3.x, with requests package
14+
15+Dependencies:
16+ ```
17+ pip install requests
18+ ```
19+
20+Options:
21+ ```
22+ $ python merger_utxo.py -h
23+usage: merger_utxo.py [-h] [-o URL] [-a ACCOUNT_ALIAS] [-p PASSWORD]
24+ [-x MAX_AMOUNT] [-s MIN_AMOUNT] [-l] [-m MERGE_LIST]
25+ [-f FOR_LOOP] [-y]
26+
27+Vapor merge utxo tool
28+
29+optional arguments:
30+ -h, --help show this help message and exit
31+ -o URL, --url URL API url to connect
32+ -a ACCOUNT_ALIAS, --account ACCOUNT_ALIAS
33+ account alias
34+ -p PASSWORD, --pass PASSWORD
35+ account password
36+ -x MAX_AMOUNT, --max MAX_AMOUNT
37+ range lower than max_amount
38+ -s MIN_AMOUNT, --min MIN_AMOUNT
39+ range higher than min_amount
40+ -l, --list Show UTXO list without merge
41+ -m MERGE_LIST, --merge MERGE_LIST
42+ UTXO to merge
43+ -f FOR_LOOP, --forloop FOR_LOOP
44+ size for loop of UTXO to merge
45+ -y, --yes confirm transfer
46+
47+ ```
48+
49+Example:
50+ ```
51+$ python btmspanner.py utxomerger -o http://127.0.0.1:9888 -a your_account_alias -p your_password -x 41250000000 -s 0 -m 20 -f 3 -y
52+ ```
53+
54+Result:
55+```
56+$ python ./merger_utxo.py -o http://127.0.0.1:9889 -a test -p 123456 -x 2000000000 -s 0 -f 1
57+ 0. 11.00000000 BTM d996363b3443407fe3828517f53551b1dda19b9e7503974892874318d03b9ed3 (mature)
58+ 1. 11.00000000 BTM d9308dae432c592e7f32ba39ddf6c7882350bbd4b0269c5b96842f1df14b31cf (mature)
59+ 2. 11.00000000 BTM c6fd223ffc2475ab0029bc5233be47c289970c141505effe20fe49f06cb575d6 (mature)
60+ 3. 11.00000000 BTM 74851bebb8e94375bd346e36718ce1a4c273ecaff97501ab1985097ff85d7eaf (mature)
61+ 4. 11.00000000 BTM 6610f18f0ecf4238568dea5a3dd1e360c5d3bff74c1bf0fefc19848a0852aabf (mature)
62+ 5. 11.00000000 BTM 4e9eb10481f1c7306ab833f632bc04134979d39605f82b40c4c107015ec15322 (mature)
63+ 6. 11.00000000 BTM 343a7d72cc830b992f8b777d32a78074a6338f642c060905997663a5c4549a8a (mature)
64+ 7. 11.00000000 BTM 338f38f99211c1da076114db3951cfccf57c6612f1ad87bafb4043d161fc9572 (mature)
65+ 8. 11.00000000 BTM 23c3d1210e636a1a30d0769c41ad2fcd25459c5f557d9fbd5b5a414d31f66ca2 (mature)
66+ 9. 11.00000000 BTM 12198e59879b139768b92bd1272fce1a6274afe43829312105b8e36ec76f8c4a (mature)
67+ 10. 11.00000000 BTM 10a420ddb64b34f7d1b3f56230d6c07e0e5c299f98c1ad5fbfe20100736128ab (mature)
68+ 11. 11.00000000 BTM 00055e2354b20a5b88c6c6914ede710b6e19575d24937325461374909446821e (mature)
69+total size of available utxos is 12
70+To merge 12 UTXOs with 132.00000000 BTM totally.
71+
72+One last disclaimer: the code we are about to go over is in no way intended to be used as an example of a robust solution.
73+You will transfer BTM to an address, please check this python code and DO IT later.
74+
75+this is the 1 times to merge utxos. -----begin
76+ 0. 11.00000000 BTM d996363b3443407fe3828517f53551b1dda19b9e7503974892874318d03b9ed3 (mature)
77+ 1. 11.00000000 BTM d9308dae432c592e7f32ba39ddf6c7882350bbd4b0269c5b96842f1df14b31cf (mature)
78+ 2. 11.00000000 BTM c6fd223ffc2475ab0029bc5233be47c289970c141505effe20fe49f06cb575d6 (mature)
79+ 3. 11.00000000 BTM 74851bebb8e94375bd346e36718ce1a4c273ecaff97501ab1985097ff85d7eaf (mature)
80+ 4. 11.00000000 BTM 6610f18f0ecf4238568dea5a3dd1e360c5d3bff74c1bf0fefc19848a0852aabf (mature)
81+ 5. 11.00000000 BTM 4e9eb10481f1c7306ab833f632bc04134979d39605f82b40c4c107015ec15322 (mature)
82+ 6. 11.00000000 BTM 343a7d72cc830b992f8b777d32a78074a6338f642c060905997663a5c4549a8a (mature)
83+ 7. 11.00000000 BTM 338f38f99211c1da076114db3951cfccf57c6612f1ad87bafb4043d161fc9572 (mature)
84+ 8. 11.00000000 BTM 23c3d1210e636a1a30d0769c41ad2fcd25459c5f557d9fbd5b5a414d31f66ca2 (mature)
85+ 9. 11.00000000 BTM 12198e59879b139768b92bd1272fce1a6274afe43829312105b8e36ec76f8c4a (mature)
86+ 10. 11.00000000 BTM 10a420ddb64b34f7d1b3f56230d6c07e0e5c299f98c1ad5fbfe20100736128ab (mature)
87+ 11. 11.00000000 BTM 00055e2354b20a5b88c6c6914ede710b6e19575d24937325461374909446821e (mature)
88+total size of available utxos is 12
89+To merge 12 UTXOs with 132.00000000 BTM
90+Confirm [y/N] y
91+tx_id: 38b38a6715ef223643a4c961b0f0553edcf6eb67b82546f741e4c391400cffa0
92+this is the 1 times to merge utxos. -----end
93+```
\ No newline at end of file
--- /dev/null
+++ b/toolbar/merger_utxo/common/Account.py
@@ -0,0 +1,95 @@
1+import json
2+
3+
4+class Account(object):
5+ def __init__(self, id, alias, key_index, quorum, xpubs=[], *args, **kwargs):
6+ self.id = id
7+ self.alias = alias
8+ self.key_index = key_index
9+ self.quorum = quorum
10+ self.xpubs = xpubs
11+
12+ @staticmethod
13+ def list(connection):
14+ response = connection.request("/list-accounts")
15+
16+ resp_json = json.loads(response.text)
17+
18+ if resp_json['status'] == 'success':
19+ account_list = list(map(lambda x: Account(**x), resp_json['data']))
20+ return account_list
21+ elif resp_json['status'] == 'fail':
22+ return resp_json['msg']
23+ else:
24+ return resp_json
25+
26+ @staticmethod
27+ def list_address(connection, account_alias, account_id):
28+
29+ body_json = {"account_alias": account_alias, "account_id": account_id}
30+
31+ response = connection.request("/list-addresses", body_json)
32+
33+ resp_json = json.loads(response.text)
34+
35+ if resp_json['status'] == 'success':
36+ return resp_json['data'], 1
37+ elif resp_json['status'] == 'fail':
38+ return resp_json['msg'], -1
39+ else:
40+ return resp_json, 0
41+
42+ @staticmethod
43+ def find_by_alias(connection, alias):
44+ account_list = Account.list(connection)
45+ for account in account_list:
46+ if account.alias == alias:
47+ return account
48+
49+ @staticmethod
50+ def find_address_by_alias(connection, account_alias):
51+ account_id = Account.find_by_alias(connection, account_alias).id
52+ address_list, ret = Account.list_address(connection, account_alias, account_id)
53+ if ret == 1:
54+ return address_list[0]['address']
55+
56+ @staticmethod
57+ def create(connection, root_xpubs, alias, quorum):
58+ body_json = {"root_xpubs": root_xpubs, "alias": alias, "quorum": quorum}
59+ response = connection.request("/create-account", body_json)
60+
61+ resp_json = json.loads(response.text)
62+
63+ if resp_json['status'] == 'success':
64+ return Account(**resp_json['data'])
65+ elif resp_json['status'] == 'fail':
66+ return resp_json['msg']
67+ else:
68+ return resp_json
69+
70+ @staticmethod
71+ def delete(connection, account_info):
72+ # String - account_info, alias or ID of account.
73+ body_json = {"account_info": account_info}
74+
75+ response = connection.request("/delete-account", body_json)
76+
77+ resp_json = json.loads(response.text)
78+
79+ if resp_json['status'] == 'success':
80+ return "true"
81+ else:
82+ return "false"
83+
84+ @staticmethod
85+ def create_address(connection, account_alias, account_id):
86+ body_json = {"account_alias": account_alias, "account_id": account_id}
87+
88+ response = connection.request("/create-account-receiver", body_json)
89+
90+ resp_json = json.loads(response.text)
91+
92+ if resp_json['status'] == "success":
93+ return resp_json['data']
94+ else:
95+ return "false"
--- /dev/null
+++ b/toolbar/merger_utxo/common/Transaction.py
@@ -0,0 +1,74 @@
1+import json
2+
3+from .Account import Account
4+
5+
6+class Transaction(object):
7+ @staticmethod
8+ def build_transaction(connection, actions):
9+ # ttl: 15min=900000ms
10+ body_json = {"base_transaction": None, "actions": actions,
11+ "ttl": 1, "time_range": 0}
12+
13+ response = connection.request("/build-transaction", body_json)
14+
15+ resp_json = json.loads(response.text)
16+
17+ if resp_json['status'] == 'success':
18+ return resp_json['data'], True
19+ elif resp_json['status'] == 'fail':
20+ return resp_json['msg'], False
21+ else:
22+ return resp_json, False
23+
24+ @staticmethod
25+ def sign_transaction(connection, password, transaction):
26+ body_json = {"password": password, "transaction": transaction}
27+ response = connection.request("/sign-transaction", body_json)
28+
29+ resp_json = json.loads(response.text)
30+
31+ if resp_json['status'] == 'success':
32+ return resp_json['data'], True
33+ elif resp_json['status'] == 'fail':
34+ return resp_json['msg'], False
35+ else:
36+ return resp_json, False
37+
38+ @staticmethod
39+ def submit_transaction(connection, raw_transaction):
40+ body_json = {"raw_transaction": raw_transaction}
41+ response = connection.request("/submit-transaction", body_json)
42+
43+ resp_json = json.loads(response.text)
44+
45+ if resp_json['status'] == 'success':
46+ return resp_json['data']
47+
48+
49+class Action(object):
50+
51+ @staticmethod
52+ def spend_account(amount, account_id, asset_id):
53+ return {
54+ 'amount': amount,
55+ 'account_id': account_id,
56+ 'asset_id': asset_id,
57+ 'type': 'spend_account'
58+ }
59+
60+ @staticmethod
61+ def control_address(amount, asset_id, address):
62+ return {
63+ 'amount': amount,
64+ 'asset_id': asset_id,
65+ 'address': address,
66+ 'type': 'control_address'
67+ }
68+
69+ @staticmethod
70+ def unspent_output(output_id):
71+ return {
72+ 'type': 'spend_account_unspent_output',
73+ 'output_id': output_id
74+ }
--- /dev/null
+++ b/toolbar/merger_utxo/common/UnspentOutputs.py
@@ -0,0 +1,29 @@
1+import json
2+
3+
4+class UnspentOutputs(object):
5+
6+ @staticmethod
7+ def get_block_height(connection):
8+ response = connection.request("/get-block-count")
9+
10+ resp_json = json.loads(response.text)
11+
12+ if resp_json['status'] == 'success':
13+ return resp_json['data']['block_count'], 1
14+ elif resp_json['status'] == 'fail':
15+ return resp_json['msg'], -1
16+ else:
17+ return resp_json, 0
18+
19+ @staticmethod
20+ def list_UTXO(connection):
21+ response = connection.request("/list-unspent-outputs")
22+
23+ resp_json = json.loads(response.text)
24+ if resp_json['status'] == 'success':
25+ return resp_json['data'], 1
26+ elif resp_json['status'] == 'fail':
27+ return resp_json['msg'], -1
28+ else:
29+ return resp_json, 0
--- /dev/null
+++ b/toolbar/merger_utxo/common/connection.py
@@ -0,0 +1,18 @@
1+import requests
2+import json
3+
4+
5+class Connection(object):
6+ def __init__(self, base_url, token=''):
7+ self.baseUrl = base_url
8+ self.token = token
9+
10+ def request(self, path, body={}):
11+ url = self.baseUrl + path
12+ headers = {}
13+ resp = requests.post(url, data=json.dumps(body), headers=headers)
14+ return resp
15+
16+ @staticmethod
17+ def generate():
18+ return Connection("http://127.0.0.1:9889")
--- /dev/null
+++ b/toolbar/merger_utxo/common/merge_utxo.py
@@ -0,0 +1,147 @@
1+import argparse
2+import getpass
3+import os
4+import time
5+
6+from .Account import Account
7+from .Transaction import Action, Transaction
8+from .UnspentOutputs import UnspentOutputs
9+from .connection import Connection
10+
11+parser = argparse.ArgumentParser(description='Vapor merge utxo tool')
12+parser.add_argument('-o', '--url', default='http://127.0.0.1:9889', dest='url', help='API url to connect')
13+parser.add_argument('-a', '--account', default=None, dest='account_alias', help='account alias')
14+parser.add_argument('-p', '--pass', default=None, dest='password', help='account password')
15+parser.add_argument('-x', '--max', default=41250000000, type=int, dest='max_amount', help='range lower than max_amount')
16+parser.add_argument('-s', '--min', default=1, type=int, dest='min_amount', help='range higher than min_amount')
17+parser.add_argument('-l', '--list', action='store_true', dest='only_list', help='Show UTXO list without merge')
18+parser.add_argument('-m', '--merge', default=90, type=int, dest='merge_list', help='UTXO to merge')
19+parser.add_argument('-f', '--forloop', default=1, type=int, dest='for_loop', help='size for loop of UTXO to merge')
20+parser.add_argument('-y', '--yes', action='store_true', default=None, dest='confirm', help='confirm transfer')
21+
22+
23+class VaporException(Exception):
24+ pass
25+
26+
27+class JSONRPCException(Exception):
28+ pass
29+
30+
31+def list_utxo(connection, account_alias, min_amount, max_amount):
32+ mature_utxos = []
33+ data, ret = UnspentOutputs.list_UTXO(connection=Connection(connection))
34+ block_height, ret_code = UnspentOutputs.get_block_height(connection=Connection(connection))
35+ if ret == 1 and ret_code == 1:
36+ for utxo in data:
37+ # append mature utxo to set
38+ if utxo['valid_height'] < block_height and utxo['account_alias'] == account_alias and utxo['asset_alias'] == 'BTM':
39+ mature_utxos.append(utxo)
40+ elif ret == -1:
41+ raise VaporException(data)
42+
43+ result = []
44+ for utxo in mature_utxos:
45+ if utxo['amount'] <= max_amount and utxo['amount'] >= min_amount:
46+ result.append(utxo)
47+
48+ return result
49+
50+
51+def send_tx(connection, utxo_list, to_address, password):
52+ actions = []
53+ amount = 0
54+ asset_id = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
55+
56+ for utxo in utxo_list:
57+ actions.append(Action.unspent_output(output_id=utxo['id']))
58+ amount += utxo['amount']
59+
60+ actions.append(Action.control_address(amount=amount, asset_id=asset_id, address=to_address))
61+
62+ time.sleep(1)
63+ transaction,f = Transaction.build_transaction(connection, actions)
64+ if f == False:
65+ return ""
66+
67+ signed_transaction,f = Transaction.sign_transaction(connection, password, transaction)
68+ if signed_transaction['sign_complete']:
69+ raw_transaction = signed_transaction['transaction']['raw_transaction']
70+ result = Transaction.submit_transaction(connection, raw_transaction)
71+ return result['tx_id']
72+ else:
73+ raise VaporException('Sign not complete')
74+
75+
76+def main():
77+ options = parser.parse_args()
78+ utxo_total = []
79+ utxolist = list_utxo(options.url, options.account_alias, options.min_amount, options.max_amount)
80+
81+ for i, utxo in enumerate(utxolist):
82+ print('{:4}. {:13.8f} BTM {}{}'.format(i, utxo['amount'] / 1e8, utxo['id'], ' (mature)'))
83+ if i >= options.merge_list * options.for_loop:
84+ break
85+ utxo_total.append(utxo)
86+
87+ print("total size of available utxos is {}".format(len(utxolist)))
88+
89+ if options.only_list:
90+ return
91+
92+ print('To merge {} UTXOs with {:13.8f} BTM totally.\n'.format(len(utxo_total),
93+ sum(utxo['amount'] for utxo in utxo_total) / 1e8))
94+
95+ merge_size = options.merge_list or input('Merge size of UTXOs (5, 13 or 20): ')
96+ for_loop = options.for_loop or input('for loop size (1, 10 or 50): ')
97+
98+ print(
99+ 'One last disclaimer: the code we are about to go over is in no way intended to be used as an example of a robust solution. ')
100+ print('You will transfer BTM to an address, please check this python code and DO IT later.\n')
101+
102+ for loops in range(for_loop):
103+
104+ utxo_mergelist = []
105+
106+ # for i in range(merge_size if merge_size <= len(utxolist) else len(utxolist)):
107+ # utxo_mergelist.append(utxolist[i])
108+ for i in range(loops * merge_size,
109+ ((loops + 1) * merge_size) if ((loops + 1) * merge_size) < len(utxolist) else len(utxolist)):
110+ utxo_mergelist.append(utxolist[i])
111+
112+ # print(loops*merge_size, ", ", ((loops+1)*merge_size) if (loops*merge_size) < len(utxolist) else len(utxolist))
113+ print('this is the {} times to merge utxos. -----begin'.format(loops + 1))
114+
115+ for i, utxo in enumerate(utxo_mergelist):
116+ print(
117+ '{:4}. {:13.8f} BTM {}{}'.format(loops * merge_size + i, utxo['amount'] / 1e8, utxo['id'], ' (mature)'))
118+
119+ print("total size of available utxos is {}".format(len(utxo_mergelist)))
120+
121+ if len(utxo_mergelist) < 2:
122+ print('Not Merge UTXOs, Exit...')
123+ return
124+
125+ print('To merge {} UTXOs with {:13.8f} BTM'.format(len(utxo_mergelist),
126+ sum(utxo['amount'] for utxo in utxo_mergelist) / 1e8))
127+
128+ if not options.account_alias:
129+ options.account_alias = input('Transfer account alias: ')
130+
131+ if not options.password:
132+ options.password = getpass.getpass('Vapor Account Password: ')
133+
134+ if not (options.confirm or input('Confirm [y/N] ').lower() == 'y'):
135+ print('Not Merge UTXOs, Exit...')
136+ return
137+
138+ to_address = Account.find_address_by_alias(Connection(options.url), options.account_alias)
139+ if not to_address:
140+ to_address = input('Transfer address: ')
141+
142+ print('tx_id:', send_tx(Connection(options.url), utxo_mergelist, to_address, options.password))
143+ print('this is the {} times to merge utxos. -----end\n'.format(loops + 1))
144+
145+
146+if __name__ == '__main__':
147+ main()
--- /dev/null
+++ b/toolbar/merger_utxo/merger_utxo.py
@@ -0,0 +1,7 @@
1+#!/usr/bin/env python3
2+
3+import sys
4+from common import merge_utxo
5+
6+if __name__ == "__main__":
7+ sys.exit(merge_utxo.main())
Show on old repository browser