Merge branch 'stratum_sidmanager'
[bitcoin:eloipool.git] / bitcoin / txn.py
1 # Eloipool - Python Bitcoin pool server
2 # Copyright (C) 2011-2012  Luke Dashjr <luke-jr+eloipool@utopios.org>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 import bitcoin.script
18 from .varlen import varlenDecode, varlenEncode
19 from util import dblsha
20 from struct import pack, unpack
21
22 _nullprev = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
23
24 class Txn:
25         def __init__(self, data=None):
26                 if data:
27                         self.data = data
28                         self.idhash()
29         
30         @classmethod
31         def new(cls):
32                 o = cls()
33                 o.version = 1
34                 o.inputs = []
35                 o.outputs = []
36                 o.locktime = 0
37                 return o
38         
39         def setCoinbase(self, sigScript, seqno = 0xffffffff, height = None):
40                 if not height is None:
41                         # NOTE: This is required to be the minimum valid length by BIP 34
42                         sigScript = bitcoin.script.encodeUNum(height) + sigScript
43                 self.inputs = ( ((_nullprev, 0xffffffff), sigScript, seqno), )
44         
45         def addInput(self, prevout, sigScript, seqno = 0xffffffff):
46                 self.inputs.append( (prevout, sigScript, seqno) )
47         
48         def addOutput(self, amount, pkScript):
49                 self.outputs.append( (amount, pkScript) )
50         
51         def disassemble(self, retExtra = False):
52                 self.version = unpack('<L', self.data[:4])[0]
53                 rc = [4]
54                 
55                 (inputCount, data) = varlenDecode(self.data[4:], rc)
56                 inputs = []
57                 for i in range(inputCount):
58                         prevout = (data[:32], unpack('<L', data[32:36])[0])
59                         rc[0] += 36
60                         (sigScriptLen, data) = varlenDecode(data[36:], rc)
61                         sigScript = data[:sigScriptLen]
62                         seqno = unpack('<L', data[sigScriptLen:sigScriptLen + 4])[0]
63                         data = data[sigScriptLen + 4:]
64                         rc[0] += sigScriptLen + 4
65                         inputs.append( (prevout, sigScript, seqno) )
66                 self.inputs = inputs
67                 
68                 (outputCount, data) = varlenDecode(data, rc)
69                 outputs = []
70                 for i in range(outputCount):
71                         amount = unpack('<Q', data[:8])[0]
72                         rc[0] += 8
73                         (pkScriptLen, data) = varlenDecode(data[8:], rc)
74                         pkScript = data[:pkScriptLen]
75                         data = data[pkScriptLen:]
76                         rc[0] += pkScriptLen
77                         outputs.append( (amount, pkScript) )
78                 self.outputs = outputs
79                 
80                 self.locktime = unpack('<L', data[:4])[0]
81                 if not retExtra:
82                         assert len(data) == 4
83                 else:
84                         assert data == self.data[rc[0]:]
85                         data = data[4:]
86                         rc[0] += 4
87                         self.data = self.data[:rc[0]]
88                         return data
89         
90         def isCoinbase(self):
91                 return len(self.inputs) == 1 and self.inputs[0][0] == (_nullprev, 0xffffffff)
92         
93         def getCoinbase(self):
94                 return self.inputs[0][1]
95         
96         def assemble(self):
97                 data = pack('<L', self.version)
98                 
99                 inputs = self.inputs
100                 data += varlenEncode(len(inputs))
101                 for prevout, sigScript, seqno in inputs:
102                         data += prevout[0] + pack('<L', prevout[1])
103                         data += varlenEncode(len(sigScript)) + sigScript
104                         data += pack('<L', seqno)
105                 
106                 outputs = self.outputs
107                 data += varlenEncode(len(outputs))
108                 for amount, pkScript in outputs:
109                         data += pack('<Q', amount)
110                         data += varlenEncode(len(pkScript)) + pkScript
111                 
112                 data += pack('<L', self.locktime)
113                 
114                 self.data = data
115                 self.idhash()
116         
117         def idhash(self):
118                 self.txid = dblsha(self.data)
119
120 # Txn tests
121 def _test():
122         d = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'
123         t = Txn(d)
124         assert t.txid == b"C\xeczW\x9fUa\xa4*~\x967\xadAVg'5\xa6X\xbe'R\x18\x18\x01\xf7#\xba3\x16\xd2"
125         t.disassemble()
126         t.assemble()
127         assert t.data == d
128         assert not t.isCoinbase()
129         t = Txn.new()
130         t.addInput((b' '*32, 0), b'INPUT')
131         t.addOutput(0x10000, b'OUTPUT')
132         t.assemble()
133         assert t.txid == b'>`\x97\xecu\x8e\xb5\xef\x19k\x17d\x96sw\xb1\xf1\x9bO\x1c6\xa0\xbe\xf7N\xed\x13j\xfdHF\x1a'
134         t.disassemble()
135         t.assemble()
136         assert t.txid == b'>`\x97\xecu\x8e\xb5\xef\x19k\x17d\x96sw\xb1\xf1\x9bO\x1c6\xa0\xbe\xf7N\xed\x13j\xfdHF\x1a'
137         assert not t.isCoinbase()
138         t = Txn.new()
139         t.setCoinbase(b'COINBASE')
140         t.addOutput(0x10000, b'OUTPUT')
141         assert t.isCoinbase()
142         assert t.getCoinbase() == b'COINBASE'
143         t.assemble()
144         assert t.txid == b'n\xb9\xdc\xef\xe9\xdb(R\x8dC~-\xef~\x88d\x15+X\x13&\xb7\xbc$\xb1h\xf3g=\x9b~V'
145
146 _test()