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 Version(js.Entity, bs.Entity): type = "version" fields = { "type": js.Str, "version": js.Int, "services": js.Int, "time": js.Int, "reciever": Address, "sender": Address, "nonce": js.Int, "subverinfo": js.Str, "finalblock": js.Int } bfields = [ ("version", bs.structfmt("<I")), ("services", bs.structfmt("<Q")), ("time", bs.structfmt("<Q")), ("reciever", Address), ("sender", Address), ("nonce", bs.structfmt("<Q")), ("subverinfo", bs.Str), ("finalblock", bs.structfmt("<I")), ] @constructor def make(self, reciever): self.version = status.protocolversion self.services = status.services self.time = int(time.time()) self.sender = status.localaddress self.reciever = reciever self.nonce = status.nonce self.subverinfo = "" self.finalblock = status.currentblock
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 AddressTimestamp(js.Entity, bs.Entity): fields = { "timestamp": js.Int, "services": js.Int, "ip": js.IPv4, "port": js.Int } bfields = [ ("timestamp", bs.structfmt("<I")), ("services", bs.structfmt("<Q")), ("ip", bs.IPv4Inv6), ("port", bs.structfmt("!H")), ] def __eq__(self, other): if isinstance(other, Address): return self.ip == other.ip and self.port == other.port return False def __hash__(self): return hash(self.ip) ^ hash(self.port) @constructor def make(self, ip, port): self.services = 1 # Always 1 in current protocol self.ip = ip self.port = port
class Address(js.Entity, bs.Entity): fields = {"services": js.Int, "ip": js.IPv4, "port": js.Int} bfields = [ ("services", bs.structfmt("<Q")), ("ip", bs.IPv4Inv6), ("port", bs.structfmt("!H")), ] @constructor def make(self, ip, port): self.services = 1 # Always 1 in current protocol self.ip = ip self.port = port
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 TxOutpoint(js.Entity, bs.Entity): fields = { "tx": js.Hash, "index": js.Int, } bfields = [ ("tx", bs.Hash), ("index", bs.structfmt("<I")), ]
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 TxOutput(js.Entity, bs.Entity): fields = { "amount": js.Int, "script": js.Bytes, } bfields = [ ("amount", bs.structfmt("<Q")), ("script", bs.VarBytes), ]
class TimedAddress(js.Entity, bs.Entity): fields = { "lastseen": js.Int, "address": Address, } bfields = [ ("lastseen", bs.structfmt("<I")), ("address", Address), ]
class TxInput(js.Entity, bs.Entity): fields = { "outpoint": TxOutpoint, "script": js.Bytes, "sequence": js.Int, } bfields = [ ("outpoint", TxOutpoint), ("script", bs.VarBytes), ("sequence", bs.structfmt("<I")), ]
class Block(js.Entity, bs.Entity): fields = { "version": js.Int, "prev": js.Hash, "merkle": js.Hash, "time": js.Int, "bits": js.Int, "nonce": js.Int, } bfields = [ ("version", bs.structfmt("<I")), ("prev", bs.Hash), ("merkle", bs.Hash), ("time", bs.structfmt("<I")), ("bits", bs.structfmt("<I")), ("nonce", bs.structfmt("<I")), ] @cachedproperty def hash(self): return doublesha(self.tobinary())
class TxOutput(js.Entity, bs.Entity): fields = { "amount": js.Int, "script": js.Bytes, } bfields = [ ("amount", bs.structfmt("<Q")), ("script", bs.VarBytes), ] def __repr__(self): return "<TxOut amount: %d>" % (self.amount)
class Version(js.Entity, bs.Entity): type = "version" fields = { "type": js.Str, "version": js.Int, "services": js.Int, "time": js.Int, "reciever": Address, "sender": Address, "nonce": js.Int, "useragent": js.Str, "finalblock": js.Int, #"realy":js.Bool } bfields = [ ("version", bs.structfmt("<I")), ("services", bs.structfmt("<Q")), ("time", bs.structfmt("<Q")), ("reciever", Address), ("sender", Address), ("nonce", bs.structfmt("<Q")), ("useragent", bs.VarBytes), ("finalblock", bs.structfmt("<I")), #("relay", bs.structfmt("<?")) ] @constructor def make(self, version=60000, sender=Address.make("0.0.0.0", 0), reciever=Address.make("0.0.0.0", 0), useragent="/pycoin:0.0.1/"): self.version = version self.services = 0 self.time = int(time.time()) self.sender = sender self.reciever = reciever self.nonce = 1234134124 self.useragent = useragent self.finalblock = 1
class TxPoint(js.Entity, bs.Entity): fields = { "tx": js.Hash, "index": js.Int, } bfields = [ ("tx", bs.Hash), ("index", bs.structfmt("<I")), ] @constructor def make(self, tx, index): self.tx = tx self.index = index
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 TxInput(js.Entity, bs.Entity): fields = { "outpoint": TxPoint, "script": js.Bytes, "sequence": js.Int, } bfields = [ ("outpoint", TxPoint), ("script", bs.VarBytes), ("sequence", bs.structfmt("<I")), ] def __repr__(self): return "<TxIn outpoint: %s:%d>" % (h2h( self.outpoint.tx), self.outpoint.index)
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 InvVect(js.Entity, bs.Entity): fields = { "objtype": js.Int, "hash": js.Hash, } bfields = [ ("objtype", bs.structfmt("<I")), ("hash", bs.Hash), ] def __eq__(self, other): if self.__class__ == other.__class__: return self.hash == other.hash and self.objtype == other.objtype return NotImplemented def __hash__(self): return hash(self.hash) @constructor def make(self, objtype, hash): self.objtype, self.hash = objtype, hash
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()))