class Tx(js.Entity, bs.Entity): type = "tx" fields = { "version": js.Int, "inputs": js.List(TxInput), "outputs": js.List(TxOutput), "locktime": js.Int, } bfields = [ ("version", bs.structfmt("<I")), ("inputs", bs.VarList(TxInput)), ("outputs", bs.VarList(TxOutput)), ("locktime", bs.structfmt("<I")), ] @cachedproperty def hash(self): return doublesha(self.tobinary()) @property def coinbase(self): return len( self.inputs ) == 1 and self.inputs[0].outpoint.tx == nullhash and self.inputs[ 0].outpoint.index == 0xffffffff def __repr__(self): return "<Tx %s, %d inputs, %d outputs, coinbase: %s>" % (h2h( self.hash), len(self.inputs), len(self.outputs), str( self.coinbase))
class Blockmsg(js.Entity, bs.Entity): type = "block" fields = {"type": js.Str, "block": Block, "txs": js.List(Tx)} bfields = [ ("block", Block), ("txs", bs.VarList(Tx)), ]
class merkelproof(js.Entity, bs.Entity): bfields = [ ("pairs", bs.VarList(bs.structfmt("<32s32s"))), ("leaf", bs.Hash), ("block", bs.Hash), ] @constructor def make(self, block, tx): self.block = block.hash self.leaf = tx.hash self.pairs = utils.get_merkel_tree(block.txs, tx.hash) @cachedproperty def merkelroot(self): return doublesha("".join(self.pairs[-1])) @cachedproperty def valid(self, n): leaf = self.leaf for pair in pairs: if leaf not in pair: return False leaf = doublesha("".join(pair)) return True
class BlockAux(js.Entity, bs.Entity): fields = { "block": Block, "txs": js.List(js.Hash), "number": js.Int, "totaldiff": js.Int, "invalid": js.Bool, "mainchain": js.Bool, "chained": js.Bool, "succ": js.Hash, } bfields = [ ("block", Block), ("txs", bs.VarList(bs.Hash)), ("number", bs.structfmt("<I")), ("totaldiff", bs.structfmt("<Q")), ("invalid", bs.structfmt("<?")), ("mainchain", bs.structfmt("<?")), ("chained", bs.structfmt("<?")), ("succ", bs.Hash), ] @constructor def make(self, block): self.block, self.txs, self.number = block, [], 2**32 - 1 self.totaldiff, self.invalid, self.mainchain = 0, False, False self.chained, self.succ = False, nullhash
class Addr(js.Entity, bs.Entity): type = "addr" fields = { "type": js.Str, "addrs": js.List(AddressTimestamp), } bfields = [ ("addrs", bs.VarList(AddressTimestamp)), ]
class Tx(js.Entity, bs.Entity): type = "tx" fields = { "version": js.Int, "inputs": js.List(TxInput), "outputs": js.List(TxOutput), "locktime": js.Int, } bfields = [ ("version", bs.structfmt("<I")), ("inputs", bs.VarList(TxInput)), ("outputs", bs.VarList(TxOutput)), ("locktime", bs.structfmt("<I")), ] @cachedproperty def hash(self): return doublesha(self.tobinary())
class Inv(js.Entity, bs.Entity): type = "inv" fields = { "type": js.Str, "objs": js.List(InvVect), } bfields = [("objs", bs.VarList(InvVect))] @constructor def make(self, objs): self.objs = objs
class Getdata(js.Entity, bs.Entity): type = "getdata" # Content-wise identical to "inv" fields = { "type": js.Str, "objs": js.List(InvVect), } bfields = [("objs", bs.VarList(InvVect))] @constructor def make(self, objs): self.objs = objs
class Tx(js.Entity, bs.Entity): fields = { "tx": msgs.Tx, "block": js.Hash, "blkindex": js.Int, #"flags":js.Int, "redeemed": js.List(js.Hash), } bfields = [ ("tx", msgs.Tx), ("block", bs.Hash), ("blkindex", bs.structfmt("<L")), #("flags", bs.VarInt), ("redeemed", bs.VarList(bs.Hash)), ]
class TxAux(js.Entity, bs.Entity): fields = { "tx": Tx, "block": js.Hash, "redeemed": js.List(js.Hash), } bfields = [ ("tx", Tx), ("block", bs.Hash), ("redeemed", bs.VarList(bs.Hash)), ] @constructor def make(self, tx): self.tx, self.block = tx, nullhash self.redeemed = [nullhash] * len(tx.outputs)
class Getblocks(js.Entity, bs.Entity): type = "getblocks" fields = { "type": js.Str, "version": js.Int, "starts": js.List(js.Hash), "end": js.Hash, } bfields = [ ("version", bs.structfmt("<I")), ("starts", bs.VarList(bs.Hash)), ("end", bs.Hash), ] @constructor def make(self, starts, end=b"\x00" * 32): self.version = 31900 self.starts = starts self.end = end
class Tx(js.Entity, bs.Entity): fields = { "tx": msgs.Tx, "block": js.Hash, "blkindex": js.Int, #"flags":js.Int, "redeemed": js.List(js.Hash), } bfields = [ ("tx", msgs.Tx), ("block", bs.Hash), ("blkindex", bs.structfmt("<L")), #("flags", bs.VarInt), ("redeemed", bs.VarList(bs.Hash)), ] @staticmethod def get_by_hash(h): """get a transaction from the database. throws KeyError, if not found. """ log.debug("getting tx %s", h2h(h)) return Tx.frombinary(txs.get(h))[0] @txn_required def put(self): """update the database record of the transaction.""" log.debug("putting tx %s", h2h(self.hash)) if not Tx.exist(self.hash): for cb in settings.NEW_TX_HOOKS: cb(self) txs.put(self.hash, self.tobinary()) @staticmethod def iter_tx(): """loop though all transactions known to the database.""" try: cur = txs.cursor() while True: try: h, data = cur.next() except KeyError: break yield Tx.frombinary(data)[0] finally: cur.close() @staticmethod @txn_required def get_or_make(txmsg): """get the database transaction, or make it from txmsg if it does not exist.""" if not Tx.exist(txmsg.hash): tx = Tx.make(txmsg) tx.put() else: tx = Tx.get_by_hash(txmsg.hash) return tx @staticmethod def exist(h): """check if a transactions exist in the database""" return txs.has_key(h) @property def hash(self): return self.tx.hash @property def hexhash(self): return h2h(self.hash) @property def confirmed(self): return self.block != nullhash @property def inputs(self): return self.tx.inputs @property def outputs(self): return self.tx.outputs @property def coinbase(self): return self.tx.coinbase def get_block(self): """get the block in which this transaction is included. returns None, if the transaction is not confirmed""" if self.confirmed: return blockchain.Block.get_by_hash(self.block) else: return None def get_confirmations(self): """get the number of confirmations this transactions haves.""" blk0 = self.get_block() if not blk0: return 0 blk1 = blockchain.get_bestblock() return blk1.number - blk0.number + 1 @txn_required def confirm(self, block, blkidx=0, coinbase=False): log.info("confirming tx %s", h2h(self.hash)) self.block = block.hash self.blkindex = blkidx if settings.TX_CHECK_SCRIPTS: self.check_signatures() if coinbase: return for inp in self.tx.inputs: inp_tx = Tx.get_by_hash(inp.outpoint.tx) inp_tx.redeem_output(inp.outpoint, self) inp_tx.put() def revert(self, block=None, coinbase=False): log.info("reverting tx %s", h2h(self.hash)) self.block = nullhash if coinbase: return for inp in self.tx.inputs: inp_tx = Tx.get_by_hash(inp.outpoint.tx) inp_tx.unredeem_output(inp.outpoint) inp_tx.put() def get_amount_out(self): return sum(i.amount for i in self.tx.outputs) def get_amount_in(self, block=None, coinbase=False): if self.coinbase: if coinbase and block: reward = 50 * COIN >> (block.number / 210000) fees = block.get_all_fees() return reward + fees else: return 0 amount = 0 for inp in self.tx.inputs: amount += Tx.get_outpoint(inp.outpoint).amount return amount def get_fee(self): if self.coinbase: return 0 return self.get_amount_in() - self.get_amount_out() def verify(self, check_spend=False, check_scripts=False, block=None, coinbase=False): if self.total_amount_out() > self.total_amount_in(block=block): return False if check_spend: pass #TODO: check if all inputs are not spend yet if check_scripts: return self.check_signatures() return True def __repr__(self): return "<Tx %s, %d inputs, %d outputs, coinbase: %s, in: %d, out: %d, fees: %d>" % ( h2h(self.hash), len(self.tx.inputs), len( self.tx.outputs), str(self.coinbase), self.get_amount_in(), self.get_amount_out(), self.get_fee()) def get_simple_hash(self, inputidx, hashtype): assert hashtype == 1 tx = msgs.Tx.frombinary(self.tx.tobinary())[0] # copy the transaction for i, inp in enumerate(tx.inputs): if i == inputidx: outp = Tx.get_outpoint(inp.outpoint) inp.script = outp.script else: inp.script = "" return doublesha(tx.tobinary() + struct.pack("<L", hashtype)) def extract_info(self, inputidx): inp = self.inputs[inputidx] outp = Tx.get_outpoint(inp.outpoint) if outp.script[:2] == "\x76\xa9" and outp.script[ -2:] == "\x88\xac": #to address address = outp.script[3:-2] rest = inp.script sig_l, rest = ord(rest[0]), rest[1:] sig, rest = rest[:sig_l], rest[sig_l:] key_l, rest = ord(rest[0]), rest[1:] key, rest = rest[:key_l], rest[key_l:] if rest != "" or len(key) != key_l or len(sig) != sig_l: return (0, None, None, None) return (1, address, sig, key) if outp.script[-1:] == "\xac": # generation rest = outp.script key_l, rest = ord(rest[0]), rest[1:] key, rest = rest[:key_l], rest[key_l:] if rest != "\xac": return (0, None, None, None) rest = inp.script sig_l, rest = ord(rest[0]), rest[1:] sig, rest = rest[:sig_l], rest[sig_l:] if rest != "": return (0, None, None, None) if len(key) != key_l or len(sig) != sig_l: return (0, None, None, None) return (2, None, sig, key) def check_signatures(self): if self.coinbase: return True for idx in range(len(self.inputs)): sc_type, address, sig, keystr = self.extract_info(idx) if sc_type == 0: log.warning( "tx input %s:%d could not be validated, could not extract info", h2h(self.hash), idx) continue if sc_type == 1: log.info("tx input %s:%d, is in address-form", h2h(self.hash), idx) if hash160(key) != address: log.warning( "tx input %s:%d is invalid, address and publickey does not match", h2h(self.hash), idx) return False elif sc_type == 2: log.info("tx input %s:%d is from coin generation", h2h(self.hash), idx) sig, hashtype = ec.load_sig(sig) if hashtype != 1: log.warning( "tx input %s:%d could not be validated, hashtype is wrong", h2h(self.hash), idx) continue simple_hash = self.get_simple_hash(idx, hashtype) key = ec.Key.from_pubkey(keystr) res = key.verify(simple_hash, sig) if res: log.info("tx input %s:%d, is valid", h2h(self.hash), idx) else: log.warning("tx input %s:%d, is invalid, signature is invalid", h2h(self.hash), idx) return False return True @constructor def make(self, tx): self.tx, self.block = tx, nullhash self.blkindex = 0 self.redeemed = [nullhash] * len(tx.outputs) def get_inpoint(self, i): return msgs.TxPoint(self.hash, i) @staticmethod def get_outpoint(outpoint, txn=None): tx = Tx.get_by_hash(outpoint.tx) assert outpoint.index < len(tx.outputs), "outpoint index out of range" return tx.outputs[outpoint.index] def is_redeemed(self, outpoint): assert self.hash == outpoint.tx, "outpoint hash does not point to tx" assert outpoint.index < len( self.tx.outputs), "outpoint index out of range" return self.redeemed[outpoint.index] != nullhash def redeem_output(self, outpoint, tx): assert self.hash == outpoint.tx, "outpoint hash does not point to tx" assert outpoint.index < len( self.tx.outputs), "outpoint index out of range" if self.redeemed[outpoint.index] != nullhash: raise TxInputAlreadySpend(outpoint) self.redeemed[outpoint.index] = tx.hash def unredeem_output(self, outpoint): assert self.hash == outpoint.tx, "outpoint hash does not point to tx" assert outpoint.index < len( self.tx.outputs), "outpoint index out of range" self.redeemed[outpoint.index] = nullhash def fully_redeemed(self): return nullhash not in self.redeemed
class Block(js.Entity, bs.Entity): fields = { "block": msgs.Block, "number": js.Int, "totaldiff": js.Int, "chain": js.Int, "txs": js.List(js.Hash), "nexts": js.List(js.Hash) } bfields = [("block", msgs.Block), ("number", bs.structfmt("<L")), ("totaldiff", bs.structfmt("<Q")), ("chain", bs.structfmt("<B")), ("txs", bs.VarList(bs.Hash)), ("nexts", bs.VarList(bs.Hash))] @property def hash(self): return self.block.hash @property def hexhash(self): return h2h(self.hash) @property def prev(self): return self.block.prev @staticmethod def iter_blocks(): i = 0 while True: try: yield Block.get_by_number(i) except KeyError: raise StopIteration i += 1 @staticmethod def get_by_hash(h): log.debug("getting block %s", h2h(h)) return Block.frombinary(chain.get(h))[0] @staticmethod def get_by_number(num): h = blknums.get(str(num)) return Block.get_by_hash(h) @staticmethod def exist_by_hash(h): return chain.has_key(h) @staticmethod def exist_by_number(num): return blknums.has_key(str(num)) @staticmethod def get_or_make(blkmsg): if not Block.exist_by_hash(blkmsg.hash): blk = Block.make(blkmsg) for txmsg in blkmsg.txs: tx = transactions.Tx.get_or_make(txmsg) if tx: tx.put() blk.link() blk.put() else: blk = Block.get_by_hash(blkmsg.hash) return blk @txn_required def put(self): if self.chain == ORPHAN_CHAIN: log.debug("trying to put orphan block in db? (bug?)") return False log.debug("putting block %s", h2h(self.hash)) chain.put(self.hash, self.tobinary()) if self.chain == MAIN_CHAIN: blknums.put(str(self.number), self.hash) def get_tx(self, idx): return transactions.Tx.get_by_hash(self.txs[idx]) def iter_tx(self, from_idx=0, to_idx=None, reverse=False, enum=False): sl = list(enumerate(self.txs[from_idx:to_idx], start=from_idx)) if reverse: sl.reverse() if enum: for blkidx, tx_h in sl: yield (blkidx, transactions.Tx.get_by_hash(tx_h)) else: for blkidx, tx_h in sl: yield transactions.Tx.get_by_hash(tx_h) def get_prev(self): return Block.get_by_hash(self.prev) def get_next_bits(self): targettimespan = 14 * 24 * 60 * 60 # 2 weeks in secounds spacing = 10 * 60 # 10 min in secounds interval = targettimespan / spacing # 2 weeks of blocks(2016) if (self.number + 1) % interval != 0: return self.block.bits log.info("DIFF: retarget, current bits: %s", hex(self.block.bits)) first_blk = self for i in range(interval - 1): first_blk = first_blk.get_prev() realtimespan = self.block.time - first_blk.block.time log.info("DIFF: timespan before limits: %d", realtimespan) if realtimespan < targettimespan / 4: realtimespan = targettimespan / 4 if realtimespan > targettimespan * 4: realtimespan = targettimespan * 4 log.info("DIFF: timespan after limits: %d", realtimespan) newtarget = (bits_to_target(self.block.bits) * realtimespan) / targettimespan if newtarget > bits_to_target(0x1d00ffff): newtarget = bits_to_target(0x1d00ffff) bits = target_to_bits(newtarget) log.info("DIFF: next bits: %s", hex(bits)) return bits def verify(self): if self.chain == INVALID_CHAIN: return False if not check_bits(self.block.bits, self.hash): return False prev_blk = self.get_prev() if prev_blk.chain == INVALID_CHAIN: return False if prev_blk.get_next_bits() != self.block.bits: return False return True @txn_required def confirm(self): log.info("confirming block %s(%d)", h2h(self.hash), self.number) if not self.verify(): raise Invalidblock() if not set_bestblock(self): log.info("block %s(%d) not good", h2h(self.hash), self.number) return False self.chain = MAIN_CHAIN self.put() run_hooks(settings.BLOCK_CONFIRM_HOOKS, self) @txn_required def revert(self): log.info("reverting block %s(%d)", h2h(self.hash), self.number) set_bestblock(self.get_prev(), check=False) if self.chain != MAIN_CHAIN: log.debug("reverting a block, that is not in main chain? (bug?)") self.chain = SIDE_CHAIN self.put() run_hooks(settings.BLOCK_REVERT_HOOKS, self) @txn_required def invalidate(self): self.chain = CHAIN_INVALID self.put() blocks_to_invalidate = set(self.nexts) while blocks_to_invalidate: blk_h = blocks_to_invalidate.pop() blk = Block.get_by_hash(blk_h) blk.chain = CHAIN_INVALID blk.put() blocks_to_invalidate.update(blk.nexts) @txn_required def link(self): prev = self.get_prev() prev.nexts.append(self.hash) prev.put() if prev.chain == MAIN_CHAIN: self.chain = SIDE_CHAIN else: self.chain = prev.chain if not self.verify(): self.invalidate() self.number = prev.number + 1 self.totaldiff = prev.totaldiff + bits_to_diff(self.block.bits) self.put() def get_all_fees(self): return sum(tx.get_fee() for tx in self.iter_tx()) def changes_since(self): bestblock = get_bestblock() split, reverted, confirmed = find_split(self, bestblock) result = {} result["reverted"] = [(blk.hash, [tx_h for tx_h in blk.txs]) for blk in reverted] result["confirmed"] = [(blk.hash, [tx_h for tx_h in blk.txs]) for blk in confirmed] return result def __repr__(self): return "<Block %s(%d) - diff: %d - chain: %s, txs: %s>" % ( self.hexhash, self.number, bits_to_diff(self.block.bits), _chains.get(self.chain, "unknown(BUG)"), len(self.txs)) @constructor def make(self, blockmsg): self.block, self.txs = blockmsg.block, [tx.hash for tx in blockmsg.txs] self.number, self.totaldiff, self.chain, self.nexts = 0, 0, ORPHAN_CHAIN, [] def tonetwork(self): return msgs.Blockmsg(self.block, list(self.iter_tx()))
class KeyEntry(bs.Entity, js.Entity): bfields = [ ("privatkey", bs.VarBytes), ("publickey", bs.VarBytes), ("txs", bs.VarList(bs.Hash)) ] fields = { "privatkey": js.Bytes, "publickey": js.Bytes, "txs": js.List(js.Hash) } @property def hash(self): return hash160(self.publickey) @staticmethod def iter_keys(txn=None): try: cur = keychain.cursor(txn=txn) while True: try: h, data = cur.next() except KeyError: break yield KeyEntry.frombinary(data)[0] finally: cur.close() def __init_key(self): self._key = ec.Key.from_privkey(self.privatkey) @property def bitcoinaddress(self): return hash2addr(self.hash) @staticmethod def get_by_hash(h, txn=None): ret = KeyEntry.frombinary(keychain.get(h, txn=txn))[0] ret.__init_key() return ret @staticmethod def get_by_publickey(key, txn=None): return KeyEntry.get_by_hash(hash160(key), txn=txn) def tosecret(self): secret = "\x80" + self._key.get_secret() if self._key.get_compressed(): secret += "\x01" secret = secret+doublesha(secret)[:4] return b58encode(secret) @classmethod def fromsecret(cls, secret): self = cls() secret = b58decode(secret, None) secret, cksum = secret[:-4], secret[-4:] if doublesha(secret)[:4] != cksum: return None valid, secret, compressed = secret[0]=="\x80", secret[1:33], secret[33:] == "\x01" if not valid: return None self._key = ec.Key.generate(secret, compressed) self.privatkey = self._key.get_privkey() self.publickey = self._key.get_pubkey() self.txs = [] return self @classmethod def generate(cls): self = cls() self._key = ec.Key.generate() self.privatkey = self._key.get_privkey() self.publickey = self._key.get_pubkey() self.txs = [] return self @txn_required def put(self, txn=None): keychain.put(self.hash, self.tobinary(), txn=txn)