Bugfix: bitcoin/script: Correctly interpret P2SH addresses, and reject anything unrec...
[bitcoin:eloipool.git] / workmaker.py
1 #!/usr/bin/python3
2 import config
3
4
5 import logging
6
7 logging.basicConfig(level=logging.DEBUG)
8
9 def RaiseRedFlags(reason):
10         logging.getLogger('redflag').critical(reason)
11         return reason
12
13
14 from bitcoin.node import BitcoinLink
15 UpstreamBitcoind = BitcoinLink( config.UpstreamBitcoindNode, config.UpstreamNetworkId )
16
17
18 from bitcoin.script import BitcoinScript
19 from bitcoin.txn import Txn
20 from base58 import b58decode
21 from struct import pack
22 import subprocess
23 from time import time
24
25 CoinbasePrefix = config.CoinbasePrefix if hasattr(config, 'CoinbasePrefix') else b''
26
27 def makeCoinbase():
28         now = int(time())
29         if now > makeCoinbase.last:
30                 makeCoinbase.last = now
31                 makeCoinbase.extranonce = 0
32         else:
33                 makeCoinbase.extranonce += 1
34         return CoinbasePrefix + pack('>L', now) + pack('>Q', makeCoinbase.extranonce).lstrip(b'\0')
35 makeCoinbase.last = 0
36
37 def makeCoinbaseTxn(coinbaseValue):
38         t = Txn.new()
39         t.setCoinbase(makeCoinbase())
40         
41         if hasattr(config, 'CoinbaserCmd'):
42                 coinbased = 0
43                 try:
44                         cmd = config.CoinbaserCmd
45                         cmd = cmd.replace('%d', str(coinbaseValue))
46                         p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
47                         nout = int(p.stdout.readline())
48                         for i in range(nout):
49                                 amount = int(p.stdout.readline())
50                                 addr = p.stdout.readline().rstrip(b'\n').decode('utf8')
51                                 pkScript = BitcoinScript.toAddress(addr)
52                                 t.addOutput(amount, pkScript)
53                                 coinbased += amount
54                 except:
55                         coinbased = coinbaseValue + 1
56                 if coinbased >= coinbaseValue:
57                         logging.getLogger('makeCoinbaseTxn').error('Coinbaser failed!')
58                         t.outputs = []
59                 else:
60                         coinbaseValue -= coinbased
61         
62         pkScript = BitcoinScript.toAddress(config.TrackerAddr)
63         t.addOutput(coinbaseValue, pkScript)
64         t.assemble()
65         
66         # TODO
67         # TODO: red flag on dupe coinbase
68         return t
69
70
71 from util import Bits2Target
72
73 workLog = {}
74 networkTarget = None
75
76 def blockChanged():
77         global MM, networkTarget
78         networkTarget = Bits2Target(MM.currentBlock[1])
79         workLog.clear()
80
81
82 from merklemaker import merkleMaker
83 MM = merkleMaker()
84 MM.__dict__.update(config.__dict__)
85 MM.makeCoinbaseTxn = makeCoinbaseTxn
86 MM.onBlockChange = blockChanged
87 MM._THISISUGLY = UpstreamBitcoind
88 MM.start()
89
90
91 from binascii import b2a_hex
92 from struct import pack, unpack
93 from time import time
94 from util import RejectedShare, dblsha, hash2int
95
96 def getBlockHeader(username):
97         MRD = MM.getMRD()
98         (merkleRoot, merkleTree, coinbaseTxn, prevBlock, bits, rollPrevBlk) = MRD
99         timestamp = pack('<L', int(time()))
100         hdr = b'\1\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
101         workLog.setdefault(username, {})[merkleRoot] = MRD
102         return hdr
103
104 def checkShare(share):
105         data = share['data']
106         (prevBlock, bits) = MM.currentBlock
107         sharePrevBlock = data[4:36]
108         if sharePrevBlock != prevBlock:
109                 if sharePrevBlock == MM.lastBlock[0]:
110                         raise RejectedShare('stale-prevblk')
111                 raise RejectedShare('bad-prevblk')
112         
113         shareMerkleRoot = data[36:68]
114         # TODO: use userid
115         username = share['username']
116         if username not in workLog:
117                 raise RejectedShare('unknown-user')
118         MWL = workLog[username]
119         if shareMerkleRoot not in MWL:
120                 raise RejectedShare('unknown-work')
121         MRD = MWL[shareMerkleRoot]
122         share['MRD'] = MRD
123         
124         shareTimestamp = unpack('<L', data[68:72])[0]
125         shareTime = share['time'] = time()
126         if shareTimestamp < shareTime - 300:
127                 raise RejectedShare('time-too-old')
128         if shareTimestamp > shareTime + 7200:
129                 raise RejectedShare('time-too-new')
130         if data[72:76] != bits:
131                 raise RejectedShare('bad-diffbits')
132         if data[:4] != b'\1\0\0\0':
133                 raise RejectedShare('bad-version')
134         
135         blkhash = dblsha(data)
136         if blkhash[28:] != b'\0\0\0\0':
137                 raise RejectedShare('H-not-zero')
138         blkhashn = hash2int(blkhash)
139         
140         global networkTarget
141         logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
142         logfunc('BLKHASH: %64x' % (blkhashn,))
143         logfunc(' TARGET: %64x' % (networkTarget,))
144         
145         if blkhashn <= networkTarget:
146                 logfunc("Submitting upstream")
147                 MRD[1].data[0] = MRD[2]
148                 UpstreamBitcoind.submitBlock(data, MRD[1].data)
149 checkShare.logger = logging.getLogger('checkShare')
150
151 def receiveShare(share):
152         # TODO: username => userid
153         checkShare(share)
154         # TODO
155
156 def newBlockNotification(signum, frame):
157         MM.updateMerkleTree()
158         # TODO: Force RESPOND TO LONGPOLLS?
159         pass
160
161 from signal import signal, SIGUSR1
162 signal(SIGUSR1, newBlockNotification)
163
164
165 from jsonrpcserver import JSONRPCServer
166 import interactivemode
167
168 if __name__ == "__main__":
169         server = JSONRPCServer(('', 8444))
170         server.getBlockHeader = getBlockHeader
171         server.receiveShare = receiveShare
172         server.RaiseRedFlags = RaiseRedFlags
173         server.serve_forever()