Initial release.
[robmyers:dogecode.git] / bin / dcsend
1 #! /usr/bin/env python3
2 # -*- mode: python -*-
3
4 # dcsend - Upload tokens to the Dogeparty blockchain.
5 # Copyright (C) 2014 Rob Myers rob@robmyers.org
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or 
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import csv, sys, time
21 from optparse import OptionParser
22 from retrying import retry
23
24 import dogecode.network, dogecode.translation
25
26 # Send poll wait time in seconds
27 POLL_WAIT_TIME = 10
28 POLL_MAX_WAITS = 100
29
30 class Uploader(object):
31     """A class to manage the uploading of a program to an addess"""
32
33     #TODO: Allow restarting upload
34     #TODO: Allow uploading to queue
35     #TODO: When waiting for send to synch, check if sent and received
36     #      token list is same length but has different contents,
37     #      fail if so.
38     
39     def __init__(self, api, from_account, to_account, csvfile, fee=0):
40         """Record the information needed to perform the upload"""
41         self.api = api
42         self.from_account = from_account
43         self.to_account = to_account
44         self.reader = csv.reader(csvfile)
45         self.fee = fee
46         self.sent_tokens = dogecode.translation.TokenList()
47         
48     def current_receiving_account_token_list(self):
49         """Get the current state of tokens sent to the account"""
50         json = self.api.token_transactions_for_address(self.to_account)
51         token_list = dogecode.translation.TokenList()
52         token_list.from_json(json)
53         return token_list
54
55     def received_tokens_match_sent_tokens(self):
56         """Check whether the tokens on the receiving account match the sent tokens"""
57         receiving_tokens = self.current_receiving_account_token_list()
58         return self.sent_tokens.equals(receiving_tokens)
59
60     def wait_for_account_state_to_match_sent_tokens(self):
61         """Poll Dogepartyd until the tokens on the receiving account match the sent tokens"""
62         sys.stderr.write("Waiting for token state to synch")
63         sys.stderr.flush()
64         try_count = 0
65         while not self.received_tokens_match_sent_tokens():
66             sys.stderr.write(".")
67             sys.stderr.flush()
68             time.sleep(POLL_WAIT_TIME)
69             try_count += 1
70             if try_count > POLL_MAX_WAITS:
71                 print("Too many failures.", file=sys.stderr)
72                 sys.exit(2)
73         print("", file=sys.stderr)
74     
75     def send_row(self, row):
76         """Send the token run described by the row"""
77         token, amount = row
78         result = self.api.send_token(self.from_account, self.to_account, token, amount, self.fee)
79         if not result:
80             raise Exception("Error calling the api.")
81         self.sent_tokens.append(token, amount)
82
83     def send(self):
84         """Send all the tokens, waiting for each send to be included in
85            the blockchain"""
86         for row in self.reader:
87             self.wait_for_account_state_to_match_sent_tokens()
88             print("Send {}: {}".format(self.reader.line_num, ",".join(row)), file=sys.stderr)
89             self.send_row(row)
90     
91 def getopts():
92     usage = "usage: %prog [options] from_account to_account csv_filename"
93     parser = OptionParser(usage=usage)
94     parser.add_option("-f", "--fee", dest="fee", default="0",
95                       help="the fee for each send")
96     parser.add_option("-r", "--rpc-host", dest="host", default="localhost",
97                       help="the Dogeparty json-rpc host, probably localhost")
98     parser.add_option("-p", "--rpc-port", dest="port", default="5000",
99                       help="the Dogeparty json-rpc port")
100     parser.add_option("-u", "--rpc-user", dest="user", default="rpc",
101                       help="the Dogeparty json-rpc username")
102     parser.add_option("-w", "--rpc-password", dest="password", default=None,
103                       help="the Dogeparty json-rpc password")
104     opts, args = parser.parse_args()
105     if(len(args) != 3):
106         parser.error("must supply from_account to_account csv_filename")
107     return opts, args
108
109 def main():
110     (opts, args) = getopts()
111     #TODO: make sure address is valid
112     user = opts.user
113     password = opts.password
114     if not password:
115         print("Enter Dogeparty json-rpc password:", file=sys.stderr)
116         password = sys.stdin.readline()
117     host = opts.host
118     port = opts.port
119     fee = int(opts.fee)
120     from_account = args[0]
121     to_account = args[1]
122     csv_filename = args[2]
123     print("Sending lots of tokens from {} to {}.".format(from_account,
124                                                          to_account),
125           file=sys.stderr)
126     print("Make sure you really want to do this.", file=sys.stderr)
127     api = dogecode.network.DogepartyApi(user, password, host, port)
128     #TODO: Check the sending account has sufficient tokens and fees
129     #TODO: Chech the receiving account has no tokens (or a flag is set)
130     with open(csv_filename, 'r') as csvfile:
131         uploader = Uploader(api, from_account, to_account, csvfile, fee)
132         uploader.send()
133         
134 if __name__ == "__main__":
135     main()