From: Scott Nadal Date: Mon, 23 Jul 2012 00:09:18 +0000 (+0100) Subject: Merge with Bitcoin v0.6.3 X-Git-Tag: v0.4.0-unstable~129 X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=commitdiff_plain;h=0561bbd1c69263dceb24ffacf850788e6e961a13;hp=6e0c5e3778b83f128f6f14c311d5728392053581 Merge with Bitcoin v0.6.3 --- diff --git a/README.ppcoin b/README.ppcoin new file mode 100644 index 0000000..e69de29 diff --git a/bitcointools/.gitignore b/bitcointools/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/bitcointools/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/bitcointools/BCDataStream.py b/bitcointools/BCDataStream.py new file mode 100644 index 0000000..cb2d1d0 --- /dev/null +++ b/bitcointools/BCDataStream.py @@ -0,0 +1,116 @@ +# +# Workalike python implementation of Bitcoin's CDataStream class. +# +import struct +import StringIO +import mmap + +class SerializationError(Exception): + """ Thrown when there's a problem deserializing or serializing """ + +class BCDataStream(object): + def __init__(self): + self.input = None + self.read_cursor = 0 + + def clear(self): + self.input = None + self.read_cursor = 0 + + def write(self, bytes): # Initialize with string of bytes + if self.input is None: + self.input = bytes + else: + self.input += bytes + + def map_file(self, file, start): # Initialize with bytes from file + self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) + self.read_cursor = start + def seek_file(self, position): + self.read_cursor = position + def close_file(self): + self.input.close() + + def read_string(self): + # Strings are encoded depending on length: + # 0 to 252 : 1-byte-length followed by bytes (if any) + # 253 to 65,535 : byte'253' 2-byte-length followed by bytes + # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes + # ... and the Bitcoin client is coded to understand: + # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string + # ... but I don't think it actually handles any strings that big. + if self.input is None: + raise SerializationError("call write(bytes) before trying to deserialize") + + try: + length = self.read_compact_size() + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return self.read_bytes(length) + + def write_string(self, string): + # Length-encoded as with read-string + self.write_compact_size(len(string)) + self.write(string) + + def read_bytes(self, length): + try: + result = self.input[self.read_cursor:self.read_cursor+length] + self.read_cursor += length + return result + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return '' + + def read_boolean(self): return self.read_bytes(1)[0] != chr(0) + def read_int16(self): return self._read_num(' : location of transactions that spend this transaction's inputs + +blockindex : key continues with uint256 block hash (32 bytes) + value is serialized CDiskBlockIndex + +version : value is integer version number + +hashBestChain : value is 32-byte SHA256 hash of last block in the longest block chain. + + +wallet.dat serialization notes: +------------------------------- +Berkely DB BTREE file (key-value pairs). + +Keys begin with a serialized string, rest of key and value depend on that first string. Possibilities are: + +name : key continues with 34-character Bitcoin "hash160" address + value is a simple string. + +tx : key continues with uint256 (32 bytes) + value is a serialized CTransaction+CMerkleTx+CWalletTx (see below for details) + +key : key continues with vector that is public key + value is vector that is private key (full 271-byte + OpenSSL representation) + +wkey : key continues with vector that is public key + value is serialized CWalletKey: + vector that is private key + int64 nTimeCreated + int64 nTimeExpires + string comment + +ckey : enCrypted key continues with vector that is public key + value is vector that is private key (32 byte private part, + not full 271-byte OpenSSL representation) + +mkey : master key continues with unsigned int that is ID of the master key + value is serialized CMasterKey: + vector encrypted key + vector encrypted salt + unsigned int indicating the method to derive key from password + unsigned int indicating the number of times the derivation algorithm should be iterated + vector holding other parameters to the derivation algorithm + +defaultkey : + value is vector default key + +version : + value is int version number + +setting : key continues with setting name, value depends on setting as follows: + addrIncoming : CAddress serialized + addrProxy : CAddress serialized + fGenerateBitcoins : 1-character (boolean) + fLimitProcessors : 1-character (boolean) + fMinimizeOnClose : 1-character (boolean) + fMinimizeToTray : 1-character (boolean) + fUseProxy : 1-character (boolean) + nLimitProcessors : int + nTransactionFee : int64 + + +Complex value type serialization: +--------------------------------- + +CDiskTxPos: + uint32 nFile : Which of the blk000*.dat files + uint32 nBlockPos : Byte position in file + uint32 nTxPos : Byte position in file + +CDiskBlockIndex: + int nVersion + uint256 hashNext + uint32 nFile : Which of the blk000*.dat files + uint32 nBlockPos : Byte position in file + int nHeight : height in block chain + int blockVersion : ??? Not sure why version is repeated... + uint256 hashPrev + uint256 hashMerkleRoot + uint32 nTime + uint32 nBits + uint32 nNonce + +CBlock: + # Note: 4 bytes: f9 be b4 d9 are written before records in blk000N.dat files + # But the nBlockPos pointers in CDiskBlockIndex points to start of serializedSize + int serializedSize + int version + uint256 hashPrev + uint256 hashMerkleRoot + uint32 nTime + uint32 nBits + uint32 nNonce + vector + +CAddress: + int nVersion + unsigned int nTime + uint64 nServices + unsigned char[12] pchReserved + unsigned int ip + unsigned short port + +CTransaction: + int nVersion + vector vin + vector vout + unsigned int nLockTime + +CTxIn: + COutPoint prevout + CScript scriptSig + unsigned int nSequence + +CTxOut: + int64 nValue + CScript scriptPubKey + +COutPoint: + 36 bytes(FLATDATA): 32-byte hash, 4-byte unsigned int + +CScript: + vector containing built-in little scripting-language script + (opcodes and arguments to do crypto stuff) + +CMerkleTx: + ... serialize CTransaction, then: + uint256 hashBlock + vector vMerkleBranch + int nIndex + +CWalletTx: + ... serialized CMerkleTx, then: + vector vtxPrev + map mapValue + vector > vOrderForm + unsigned int fTimeReceivedIsTxTime + unsigned int nTimeReceived + char fFromMe + char fSpent + +CInv: + int type : 1:tx 2:block + uint256 hash + +CBlockLocator: + int nVersion + vector # Block hashes, newest back to genesis block (dense to start, but then sparse) + +string: + 1/3/5/9 bytes giving length: + 1 byte if length < 253. + otherwise byte value '253'+ushort, '254'+uint, '255'+uint64 + then length bytes. + +vector: + 1/3/5/9 bytes giving count (see string, above) + followed by that many serialized one-after-another + +pair: + just first item followed by second item + + +PUBLIC KEYS TO BITCOIN ADDRESSES +-------------------------------- + +Public key, in memory (65 bytes): + +0x94c7818: 0x04 0x57 0xcc 0xad 0xd7 0x1e 0xb0 0xf3 +0x94c7820: 0xc1 0x9d 0x22 0xb9 0xba 0x0e 0xa1 0xf3 +0x94c7828: 0x44 0x2a 0x6f 0x12 0x31 0x46 0xb5 0xbd +0x94c7830: 0xff 0x10 0x60 0xbc 0xd1 0x11 0x68 0xe6 +0x94c7838: 0x6a 0x71 0xbe 0xd4 0xda 0x17 0x7c 0x12 +0x94c7840: 0xd7 0x30 0x9a 0xdd 0xfd 0xf5 0x6c 0x31 +0x94c7848: 0xd5 0xc8 0xa2 0x7b 0x8e 0x6a 0x22 0x20 +0x94c7850: 0x38 0x42 0xc6 0xc2 0x4f 0xd5 0x9b 0xd7 +0x94c7858: 0xb7 +Python string: +public_key = "\x04\x57\xcc\xad\xd7\x1e\xb0\xf3\xc1\x9d\x22\xb9\xba\x0e\xa1\xf3\x44\x2a\x6f\x12\x31\x46\xb5\xbd\xff\x10\x60\xbc\xd1\x11\x68\xe6\x6a\x71\xbe\xd4\xda\x17\x7c\x12\xd7\x30\x9a\xdd\xfd\xf5\x6c\x31\xd5\xc8\xa2\x7b\x8e\x6a\x22\x20\x38\x42\xc6\xc2\x4f\xd5\x9b\xd7\xb7" + +SHA256 hash of that is: +0xb6890938: 0x0d 0x72 0xab 0x02 0xc8 0xab 0x52 0xce +0xb6890940: 0x7e 0x6b 0x04 0x00 0x95 0x58 0x09 0xf0 +0xb6890948: 0x93 0x48 0x21 0xb6 0x26 0xc3 0x27 0xc7 +0xb6890950: 0x9a 0x07 0x62 0xfd 0xbc 0x5e 0xb8 0xa5 +h1 = SHA256.new(public_key).digest() + +RIPEMD160(SHA256(public key)) is: +0xb68909ac: 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 0xa2 +0xb68909b4: 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 0x67 +0xb68909bc: 0x28 0x26 0x30 0xd3 +h2 = RIPEMD160.new(h1).digest() + +Put version number '0x00' byte onto front: +0x9eeb840: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 +0x9eeb848: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 +0x9eeb850: 0x67 0x28 0x26 0x30 0xd3 +vh2 = "\x00"+h2 + +Hash (double-SHA256) that: +0xb68908e8: 0xf9 0xb7 0x8e 0x64 0x6e 0x20 0x27 0xc4 +0xb68908f0: 0xaa 0x62 0x66 0x04 0x2e 0xb6 0xa2 0xe0 +0xb68908f8: 0x41 0x03 0x9d 0xd8 0xe2 0x24 0x24 0xe8 +0xb6890900: 0x50 0xac 0x20 0x29 0xfb 0xcd 0xb4 0x6e +h3=SHA256.new(SHA256.new(vh2).digest()).digest() + +Add first 4 bytes from Hash object onto end as check-bytes: +0x9fa6628: 0x00 0x5c 0xc8 0x7f 0x4a 0x3f 0xdf 0xe3 +0x9fa6630: 0xa2 0x34 0x6b 0x69 0x53 0x26 0x7c 0xa8 +0x9fa6638: 0x67 0x28 0x26 0x30 0xd3 0xf9 0xb7 0x8e +0x9fa6640: 0x64 +addr=vh2+h3[0:4] + +Result length should be: int(math.floor(len(addr)*8/math.log(58,2))) +Base58 encode that, front-pad with '1's if less than expected length +to get: 19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT + + +WIRE PROTOCOL NOTES +------------------- +Default port is 8333 + +// Message format +// (4) message start { 0xf9, 0xbe, 0xb4, 0xd9 } +// (12) command +// (4) size -- number of bytes of data +// (4) checksum -- First four bytes of double SHA256 hash of data +// (x) data + + --> messages might be split by network layer... + +Commands are: + +"version" : + int nVersion + uint64 nServices + int64 nTime + CAddress addrMe + CAddress addrFrom # if nVersion > 106 + uint64 nNonce + string strSubVer # if nVersion > 106 + int nStartingHeight # if nVersion > 209 + +nNonce is random value (I think), used to detect self-connection. + +"verack" : no data. Sent to sync min version (peer, peer) + +"addr" : + vector + (relayed to 10 random nodes so they spread) + +"inv" : + vector + +"getdata" : Asks for blocks or tx's (response is "block" or "tx" messages) + vector + +"getblocks" : + CBLockLocator + uint256 hashStop + +"tx" : + CTransaction + +"block" : + CBlock + +"getaddr" : no data ("please send me addr messages") + +"checkorder" + uint256 hashReply + CWalletTx order + +"submitorder" + uint256 hashReply + CWalletTx wtxNew + +"reply" + uint256 hashReply + +"ping" : no data (and no reply) + + +TRANSACTION SIGNING NOTES +------------------------- + +So, I want to spend some bitcoin. +First, I gotta have a Transaction to me-- specifically, a Transaction with a TxOut that contains a scriptPubKey that I can satisfy. + +TxOut.scriptPubKey's come in a couple different flavors: + DUP HASH160 {hash160(public_key) EQUALVERIFY CHECKSIG + --> I have to be able to supply a matching TxIn.scriptSig with: signature and public key + (public key) CHECKSIG + --> I have to supply a matching TxIn.scriptSig with: signature + +TODO: figure out what, exactly, is hashed (and how), and how, exactly, that hash value is signed with the private key. + +DIFFICULTY, NBITS, BN_mpi2bn +---------------------------- + +Hash targets are stored as "compact bignums;" the production block chain has an initial target of: +0x1d00ffff ... which means "create a bignum that is 0x1d (29) bytes long and has 0x00ffff as the +three bytes on the big end." Or: 0xffff0000000000000000000000000000000000000000000000000000 + +As I write this, the current block->nBits is 0x1c010c5. To compute difficulty: + php -r '$nBits = 0x1c010c5a; print( (0xffff << ((0x1d-($nBits>>24))*8)) / ($nBits&0xffffff) );' + + diff --git a/bitcointools/README.txt b/bitcointools/README.txt new file mode 100644 index 0000000..0939cf3 --- /dev/null +++ b/bitcointools/README.txt @@ -0,0 +1,42 @@ +----- dbdump.py ----- +Run dbdump.py --help for usage. Database files are opened read-only, but +you might want to backup your Bitcoin wallet.dat file just in case. + +You must quit Bitcoin before reading the transactions, blocks, or address database files. + +Requires the pycrypto library from http://www.dlitz.net/software/pycrypto/ +to translate public keys into human-friendly Bitcoin addresses. + +Examples: + +Print out wallet keys and transactions: + dbdump.py --wallet --wallet-tx + +Print out the "genesis block" (the very first block in the proof-of-work block chain): + dbdump.py --block=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f + +Print out one of the transactions from my wallet: + dbdump.py --transaction=c6e1bf883bceef0aa05113e189982055d9ba7212ddfc879798616a0d0828c98c + dbdump.py --transaction=c6e1...c98c + +Print out all 'received' transactions that aren't yet spent: + dbdump.py --wallet-tx-filter='fromMe:False.*spent:False' + +Print out all blocks involving transactions to the Bitcoin Faucet: + dbdump.py --search-blocks=15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC + +There's a special search term to look for non-standard transactions: + dbdump.py --search-blocks=NONSTANDARD_CSCRIPTS + +----- statistics.py ----- +Scan all the transactions in the block chain and dump out a .csv file that shows transaction volume per month. + +----- fixwallet.py ----- +Half-baked utility that reads a wallet.dat and writes out a new wallet.dat. + +Only half-baked because to be really useful I'd have to write serialize routines to re-pack data after modifying it... + +----- jsonToCSV.py ----- +Read JSON list-of-objects from standard input, writes CSV file to standard output. +Useful for converting bitcoind's listtransactions output to CSV that can be +imported into a spreadsheet. diff --git a/bitcointools/__init__.py b/bitcointools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bitcointools/address.py b/bitcointools/address.py new file mode 100644 index 0000000..8fca690 --- /dev/null +++ b/bitcointools/address.py @@ -0,0 +1,42 @@ +# +# Code for parsing the addr.dat file +# NOTE: I think you have to shutdown the Bitcoin client to +# successfully read addr.dat... +# + +from bsddb.db import * +import logging +from operator import itemgetter +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address +from util import short_hex +from deserialize import * + +def dump_addresses(db_env): + db = DB(db_env) + try: + r = db.open("addr.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open addr.dat/main. Try quitting Bitcoin and running this again.") + sys.exit(1) + + kds = BCDataStream() + vds = BCDataStream() + + for (key, value) in db.items(): + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + + if type == "addr": + d = parse_CAddress(vds) + print(deserialize_CAddress(d)) + + db.close() diff --git a/bitcointools/base58.py b/bitcointools/base58.py new file mode 100644 index 0000000..0d034b8 --- /dev/null +++ b/bitcointools/base58.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +"""encode/decode base58 in the same way that Bitcoin does""" + +import math + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58base = len(__b58chars) + +def b58encode(v): + """ encode v, which is a string of bytes, to base58. + """ + + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * ord(c) + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + # Bitcoin does a little leading-zero-compression: + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in v: + if c == '\0': nPad += 1 + else: break + + return (__b58chars[0]*nPad) + result + +def b58decode(v, length): + """ decode v into a string of len bytes + """ + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += __b58chars.find(c) * (__b58base**i) + + result = '' + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = chr(mod) + result + long_value = div + result = chr(long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: nPad += 1 + else: break + + result = chr(0)*nPad + result + if length is not None and len(result) != length: + return None + + return result + +try: + # Python Crypto library is at: http://www.dlitz.net/software/pycrypto/ + # Needed for RIPEMD160 hash function, used to compute + # Bitcoin addresses from internal public keys. + import Crypto.Hash.SHA256 as SHA256 + import Crypto.Hash.RIPEMD160 as RIPEMD160 + have_crypto = True +except ImportError: + have_crypto = False + +def hash_160(public_key): + if not have_crypto: + return '' + h1 = SHA256.new(public_key).digest() + h2 = RIPEMD160.new(h1).digest() + return h2 + +def public_key_to_bc_address(public_key): + if not have_crypto: + return '' + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160) + +def hash_160_to_bc_address(h160): + if not have_crypto: + return '' + vh160 = "\x00"+h160 # \x00 is version 0 + h3=SHA256.new(SHA256.new(vh160).digest()).digest() + addr=vh160+h3[0:4] + return b58encode(addr) + +def bc_address_to_hash_160(addr): + bytes = b58decode(addr, 25) + return bytes[1:21] + +if __name__ == '__main__': + x = '005cc87f4a3fdfe3a2346b6953267ca867282630d3f9b78e64'.decode('hex_codec') + encoded = b58encode(x) + print encoded, '19TbMSWwHvnxAKy12iNm3KdbGfzfaMFViT' + print b58decode(encoded, len(x)).encode('hex_codec'), x.encode('hex_codec') diff --git a/bitcointools/blkindex.py b/bitcointools/blkindex.py new file mode 100644 index 0000000..faf1113 --- /dev/null +++ b/bitcointools/blkindex.py @@ -0,0 +1,55 @@ +# +# Code for parsing the blkindex.dat file +# + +from bsddb.db import * +import logging +from operator import itemgetter +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address +from util import short_hex +from deserialize import * + +def dump_blkindex_summary(db_env): + db = DB(db_env) + try: + r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") + sys.exit(1) + + kds = BCDataStream() + vds = BCDataStream() + + n_tx = 0 + n_blockindex = 0 + + print("blkindex file summary:") + for (key, value) in db.items(): + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + + if type == "tx": + n_tx += 1 + elif type == "blockindex": + n_blockindex += 1 + elif type == "version": + version = vds.read_int32() + print(" Version: %d"%(version,)) + elif type == "hashBestChain": + hash = vds.read_bytes(32) + print(" HashBestChain: %s"%(hash.encode('hex_codec'),)) + else: + logging.warn("blkindex: unknown type '%s'"%(type,)) + continue + + print(" %d transactions, %d blocks."%(n_tx, n_blockindex)) + db.close() diff --git a/bitcointools/block.py b/bitcointools/block.py new file mode 100644 index 0000000..bff0744 --- /dev/null +++ b/bitcointools/block.py @@ -0,0 +1,240 @@ +# +# Code for dumping a single block, given its ID (hash) +# + +from bsddb.db import * +import logging +import os.path +import re +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address +from util import short_hex, long_hex +from deserialize import * + +def _open_blkindex(db_env): + db = DB(db_env) + try: + r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) + except DBError: + r = True + if r is not None: + logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") + sys.exit(1) + return db + +def _read_CDiskTxPos(stream): + n_file = stream.read_uint32() + n_block_pos = stream.read_uint32() + n_tx_pos = stream.read_uint32() + return (n_file, n_block_pos, n_tx_pos) + +def _dump_block(datadir, nFile, nBlockPos, hash256, hashNext, do_print=True): + blockfile = open(os.path.join(datadir, "blk%04d.dat"%(nFile,)), "rb") + ds = BCDataStream() + ds.map_file(blockfile, nBlockPos) + d = parse_Block(ds) + block_string = deserialize_Block(d) + ds.close_file() + blockfile.close() + if do_print: + print "BLOCK "+long_hex(hash256[::-1]) + print "Next block: "+long_hex(hashNext[::-1]) + print block_string + return block_string + +def _parse_block_index(vds): + d = {} + d['version'] = vds.read_int32() + d['hashNext'] = vds.read_bytes(32) + d['nFile'] = vds.read_uint32() + d['nBlockPos'] = vds.read_uint32() + d['nHeight'] = vds.read_int32() + + header_start = vds.read_cursor + d['b_version'] = vds.read_int32() + d['hashPrev'] = vds.read_bytes(32) + d['hashMerkle'] = vds.read_bytes(32) + d['nTime'] = vds.read_int32() + d['nBits'] = vds.read_int32() + d['nNonce'] = vds.read_int32() + header_end = vds.read_cursor + d['__header__'] = vds.input[header_start:header_end] + return d + +def dump_block(datadir, db_env, block_hash): + """ Dump a block, given hexadecimal hash-- either the full hash + OR a short_hex version of the it. + """ + db = _open_blkindex(db_env) + + kds = BCDataStream() + vds = BCDataStream() + + n_blockindex = 0 + + key_prefix = "\x0ablockindex" + cursor = db.cursor() + (key, value) = cursor.set_range(key_prefix) + + while key.startswith(key_prefix): + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + hash256 = kds.read_bytes(32) + hash_hex = long_hex(hash256[::-1]) + block_data = _parse_block_index(vds) + + if (hash_hex.startswith(block_hash) or short_hex(hash256[::-1]).startswith(block_hash)): + print "Block height: "+str(block_data['nHeight']) + _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], hash256, block_data['hashNext']) + + (key, value) = cursor.next() + + db.close() + +def read_block(db_cursor, hash): + (key,value) = db_cursor.set_range("\x0ablockindex"+hash) + vds = BCDataStream() + vds.clear(); vds.write(value) + block_data = _parse_block_index(vds) + block_data['hash256'] = hash + return block_data + +def scan_blocks(datadir, db_env, callback_fn): + """ Scan through blocks, from last through genesis block, + calling callback_fn(block_data) for each. + callback_fn should return False if scanning should + stop, True if it should continue. + Returns last block_data scanned. + """ + db = _open_blkindex(db_env) + + kds = BCDataStream() + vds = BCDataStream() + + # Read the hashBestChain record: + cursor = db.cursor() + (key, value) = cursor.set_range("\x0dhashBestChain") + vds.write(value) + hashBestChain = vds.read_bytes(32) + + block_data = read_block(cursor, hashBestChain) + + while callback_fn(block_data): + if block_data['nHeight'] == 0: + break; + block_data = read_block(cursor, block_data['hashPrev']) + return block_data + + +def dump_block_n(datadir, db_env, block_number): + """ Dump a block given block number (== height, genesis block is 0) + """ + def scan_callback(block_data): + return not block_data['nHeight'] == block_number + + block_data = scan_blocks(datadir, db_env, scan_callback) + + print "Block height: "+str(block_data['nHeight']) + _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], block_data['hash256'], block_data['hashNext']) + +def search_blocks(datadir, db_env, pattern): + """ Dump a block given block number (== height, genesis block is 0) + """ + db = _open_blkindex(db_env) + kds = BCDataStream() + vds = BCDataStream() + + # Read the hashBestChain record: + cursor = db.cursor() + (key, value) = cursor.set_range("\x0dhashBestChain") + vds.write(value) + hashBestChain = vds.read_bytes(32) + block_data = read_block(cursor, hashBestChain) + + if pattern == "NONSTANDARD_CSCRIPTS": # Hack to look for non-standard transactions + search_odd_scripts(datadir, cursor, block_data) + return + + while True: + block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], + block_data['hash256'], block_data['hashNext'], False) + + if re.search(pattern, block_string) is not None: + print "MATCH: Block height: "+str(block_data['nHeight']) + print block_string + + if block_data['nHeight'] == 0: + break + block_data = read_block(cursor, block_data['hashPrev']) + +def search_odd_scripts(datadir, cursor, block_data): + """ Look for non-standard transactions """ + while True: + block_string = _dump_block(datadir, block_data['nFile'], block_data['nBlockPos'], + block_data['hash256'], block_data['hashNext'], False) + + found_nonstandard = False + for m in re.finditer(r'TxIn:(.*?)$', block_string, re.MULTILINE): + s = m.group(1) + if re.match(r'\s*COIN GENERATED coinbase:\w+$', s): continue + if re.match(r'.*sig: \d+:\w+...\w+ \d+:\w+...\w+$', s): continue + if re.match(r'.*sig: \d+:\w+...\w+$', s): continue + print "Nonstandard TxIn: "+s + found_nonstandard = True + break + + for m in re.finditer(r'TxOut:(.*?)$', block_string, re.MULTILINE): + s = m.group(1) + if re.match(r'.*Script: DUP HASH160 \d+:\w+...\w+ EQUALVERIFY CHECKSIG$', s): continue + if re.match(r'.*Script: \d+:\w+...\w+ CHECKSIG$', s): continue + print "Nonstandard TxOut: "+s + found_nonstandard = True + break + + if found_nonstandard: + print "NONSTANDARD TXN: Block height: "+str(block_data['nHeight']) + print block_string + + if block_data['nHeight'] == 0: + break + block_data = read_block(cursor, block_data['hashPrev']) + +def check_block_chain(db_env): + """ Make sure hashPrev/hashNext pointers are consistent through block chain """ + db = _open_blkindex(db_env) + + kds = BCDataStream() + vds = BCDataStream() + + # Read the hashBestChain record: + cursor = db.cursor() + (key, value) = cursor.set_range("\x0dhashBestChain") + vds.write(value) + hashBestChain = vds.read_bytes(32) + + back_blocks = [] + + block_data = read_block(cursor, hashBestChain) + + while block_data['nHeight'] > 0: + back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) ) + block_data = read_block(cursor, block_data['hashPrev']) + + back_blocks.append( (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) ) + genesis_block = block_data + + print("check block chain: genesis block merkle hash is: %s"%(block_data['hashMerkle'][::-1].encode('hex_codec'))) + + while block_data['hashNext'] != ('\0'*32): + forward = (block_data['nHeight'], block_data['hashMerkle'], block_data['hashPrev'], block_data['hashNext']) + back = back_blocks.pop() + if forward != back: + print("Forward/back block mismatch at height %d!"%(block_data['nHeight'],)) + print(" Forward: "+str(forward)) + print(" Back: "+str(back)) + block_data = read_block(cursor, block_data['hashNext']) diff --git a/bitcointools/blocks.py b/bitcointools/blocks.py new file mode 100644 index 0000000..8384751 --- /dev/null +++ b/bitcointools/blocks.py @@ -0,0 +1,59 @@ +# +# Code for parsing the blkindex.dat file +# + +from bsddb.db import * +import logging +from operator import itemgetter +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address +from util import short_hex +from deserialize import * + +def _read_CDiskTxPos(stream): + n_file = stream.read_uint32() + n_block_pos = stream.read_uint32() + n_tx_pos = stream.read_uint32() + return (n_file, n_block_pos, n_tx_pos) + +def dump_blockindex(db_env, owner=None, n_to_dump=1000): + db = DB(db_env) + r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) + if r is not None: + logging.error("Couldn't open blkindex.dat/main") + sys.exit(1) + + kds = BCDataStream() + vds = BCDataStream() + + wallet_transactions = [] + + for (i, (key, value)) in enumerate(db.items()): + if i > n_to_dump: + break + + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + + if type == "tx": + hash256 = kds.read_bytes(32) + version = vds.read_uint32() + tx_pos = _read_CDiskTxPos(vds) + print("Tx(%s:%d %d %d)"%((short_hex(hash256),)+tx_pos)) + n_tx_out = vds.read_compact_size() + for i in range(0,n_tx_out): + tx_out = _read_CDiskTxPos(vds) + if tx_out[0] != 0xffffffffL: # UINT_MAX means no TxOuts (unspent) + print(" ==> TxOut(%d %d %d)"%tx_out) + + else: + logging.warn("blkindex: type %s"%(type,)) + continue + + db.close() + diff --git a/bitcointools/dbdump.py b/bitcointools/dbdump.py new file mode 100755 index 0000000..79340da --- /dev/null +++ b/bitcointools/dbdump.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# Code for dumping the bitcoin Berkeley db files in a human-readable format +# +from bsddb.db import * +import logging +import sys + +from address import dump_addresses +from wallet import dump_wallet, dump_accounts +from blkindex import dump_blkindex_summary +from transaction import dump_transaction +from block import dump_block, dump_block_n, search_blocks, check_block_chain +from util import determine_db_dir, create_env + +def main(): + import optparse + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--datadir", dest="datadir", default=None, + help="Look for files here (defaults to bitcoin default)") + parser.add_option("--wallet", action="store_true", dest="dump_wallet", default=False, + help="Print out contents of the wallet.dat file") + parser.add_option("--wallet-tx", action="store_true", dest="dump_wallet_tx", default=False, + help="Print transactions in the wallet.dat file") + parser.add_option("--wallet-tx-filter", action="store", dest="wallet_tx_filter", default="", + help="Only print transactions that match given string/regular expression") + parser.add_option("--accounts", action="store_true", dest="dump_accounts", default="", + help="Print out account names, one per line") + parser.add_option("--blkindex", action="store_true", dest="dump_blkindex", default=False, + help="Print out summary of blkindex.dat file") + parser.add_option("--check-block-chain", action="store_true", dest="check_chain", default=False, + help="Scan back and forward through the block chain, looking for inconsistencies") + parser.add_option("--address", action="store_true", dest="dump_addr", default=False, + help="Print addresses in the addr.dat file") + parser.add_option("--transaction", action="store", dest="dump_transaction", default=None, + help="Dump a single transaction, given hex transaction id (or abbreviated id)") + parser.add_option("--block", action="store", dest="dump_block", default=None, + help="Dump a single block, given its hex hash (or abbreviated hex hash) OR block height") + parser.add_option("--search-blocks", action="store", dest="search_blocks", default=None, + help="Search the block chain for blocks containing given regex pattern") + (options, args) = parser.parse_args() + + if options.datadir is None: + db_dir = determine_db_dir() + else: + db_dir = options.datadir + + try: + db_env = create_env(db_dir) + except DBNoSuchFileError: + logging.error("Couldn't open " + db_dir) + sys.exit(1) + + dump_tx = options.dump_wallet_tx + if len(options.wallet_tx_filter) > 0: + dump_tx = True + if options.dump_wallet or dump_tx: + dump_wallet(db_env, options.dump_wallet, dump_tx, options.wallet_tx_filter) + if options.dump_accounts: + dump_accounts(db_env) + + if options.dump_addr: + dump_addresses(db_env) + + if options.check_chain: + check_block_chain(db_env) + + if options.dump_blkindex: + dump_blkindex_summary(db_env) + + if options.dump_transaction is not None: + dump_transaction(db_dir, db_env, options.dump_transaction) + + if options.dump_block is not None: + if len(options.dump_block) < 7: # Probably an integer... + try: + dump_block_n(db_dir, db_env, int(options.dump_block)) + except ValueError: + dump_block(db_dir, db_env, options.dump_block) + else: + dump_block(db_dir, db_env, options.dump_block) + + if options.search_blocks is not None: + search_blocks(db_dir, db_env, options.search_blocks) + + db_env.close() + +if __name__ == '__main__': + main() diff --git a/bitcointools/deserialize.py b/bitcointools/deserialize.py new file mode 100644 index 0000000..a1764da --- /dev/null +++ b/bitcointools/deserialize.py @@ -0,0 +1,303 @@ +# +# +# + +from BCDataStream import * +from enumeration import Enumeration +from base58 import public_key_to_bc_address, hash_160_to_bc_address +import socket +import time +from util import short_hex, long_hex + +def parse_CAddress(vds): + d = {} + d['nVersion'] = vds.read_int32() + d['nTime'] = vds.read_uint32() + d['nServices'] = vds.read_uint64() + d['pchReserved'] = vds.read_bytes(12) + d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) + d['port'] = vds.read_uint16() + return d + +def deserialize_CAddress(d): + return d['ip']+":"+str(d['port'])+" (lastseen: %s)"%(time.ctime(d['nTime']),) + +def parse_setting(setting, vds): + if setting[0] == "f": # flag (boolean) settings + return str(vds.read_boolean()) + elif setting == "addrIncoming": + return "" # bitcoin 0.4 purposely breaks addrIncoming setting in encrypted wallets. + elif setting[0:4] == "addr": # CAddress + d = parse_CAddress(vds) + return deserialize_CAddress(d) + elif setting == "nTransactionFee": + return vds.read_int64() + elif setting == "nLimitProcessors": + return vds.read_int32() + return 'unknown setting' + +def parse_TxIn(vds): + d = {} + d['prevout_hash'] = vds.read_bytes(32) + d['prevout_n'] = vds.read_uint32() + d['scriptSig'] = vds.read_bytes(vds.read_compact_size()) + d['sequence'] = vds.read_uint32() + return d +def deserialize_TxIn(d, transaction_index=None, owner_keys=None): + if d['prevout_hash'] == "\x00"*32: + result = "TxIn: COIN GENERATED" + result += " coinbase:"+d['scriptSig'].encode('hex_codec') + elif transaction_index is not None and d['prevout_hash'] in transaction_index: + p = transaction_index[d['prevout_hash']]['txOut'][d['prevout_n']] + result = "TxIn: value: %f"%(p['value']/1.0e8,) + result += " prev("+long_hex(d['prevout_hash'][::-1])+":"+str(d['prevout_n'])+")" + else: + result = "TxIn: prev("+long_hex(d['prevout_hash'][::-1])+":"+str(d['prevout_n'])+")" + pk = extract_public_key(d['scriptSig']) + result += " pubkey: "+pk + result += " sig: "+decode_script(d['scriptSig']) + if d['sequence'] < 0xffffffff: result += " sequence: "+hex(d['sequence']) + return result + +def parse_TxOut(vds): + d = {} + d['value'] = vds.read_int64() + d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size()) + return d + +def deserialize_TxOut(d, owner_keys=None): + result = "TxOut: value: %f"%(d['value']/1.0e8,) + pk = extract_public_key(d['scriptPubKey']) + result += " pubkey: "+pk + result += " Script: "+decode_script(d['scriptPubKey']) + if owner_keys is not None: + if pk in owner_keys: result += " Own: True" + else: result += " Own: False" + return result + +def parse_Transaction(vds): + d = {} + d['version'] = vds.read_int32() + n_vin = vds.read_compact_size() + d['txIn'] = [] + for i in xrange(n_vin): + d['txIn'].append(parse_TxIn(vds)) + n_vout = vds.read_compact_size() + d['txOut'] = [] + for i in xrange(n_vout): + d['txOut'].append(parse_TxOut(vds)) + d['lockTime'] = vds.read_uint32() + return d +def deserialize_Transaction(d, transaction_index=None, owner_keys=None): + result = "%d tx in, %d out\n"%(len(d['txIn']), len(d['txOut'])) + for txIn in d['txIn']: + result += deserialize_TxIn(txIn, transaction_index) + "\n" + for txOut in d['txOut']: + result += deserialize_TxOut(txOut, owner_keys) + "\n" + return result + +def parse_MerkleTx(vds): + d = parse_Transaction(vds) + d['hashBlock'] = vds.read_bytes(32) + n_merkleBranch = vds.read_compact_size() + d['merkleBranch'] = vds.read_bytes(32*n_merkleBranch) + d['nIndex'] = vds.read_int32() + return d + +def deserialize_MerkleTx(d, transaction_index=None, owner_keys=None): + tx = deserialize_Transaction(d, transaction_index, owner_keys) + result = "block: "+(d['hashBlock'][::-1]).encode('hex_codec') + result += " %d hashes in merkle branch\n"%(len(d['merkleBranch'])/32,) + return result+tx + +def parse_WalletTx(vds): + d = parse_MerkleTx(vds) + n_vtxPrev = vds.read_compact_size() + d['vtxPrev'] = [] + for i in xrange(n_vtxPrev): + d['vtxPrev'].append(parse_MerkleTx(vds)) + + d['mapValue'] = {} + n_mapValue = vds.read_compact_size() + for i in xrange(n_mapValue): + key = vds.read_string() + value = vds.read_string() + d['mapValue'][key] = value + n_orderForm = vds.read_compact_size() + d['orderForm'] = [] + for i in xrange(n_orderForm): + first = vds.read_string() + second = vds.read_string() + d['orderForm'].append( (first, second) ) + d['fTimeReceivedIsTxTime'] = vds.read_uint32() + d['timeReceived'] = vds.read_uint32() + d['fromMe'] = vds.read_boolean() + d['spent'] = vds.read_boolean() + + return d + +def deserialize_WalletTx(d, transaction_index=None, owner_keys=None): + result = deserialize_MerkleTx(d, transaction_index, owner_keys) + result += "%d vtxPrev txns\n"%(len(d['vtxPrev']),) + result += "mapValue:"+str(d['mapValue']) + if len(d['orderForm']) > 0: + result += "\n"+" orderForm:"+str(d['orderForm']) + result += "\n"+"timeReceived:"+time.ctime(d['timeReceived']) + result += " fromMe:"+str(d['fromMe'])+" spent:"+str(d['spent']) + return result + +# The CAuxPow (auxiliary proof of work) structure supports merged mining. +# A flag in the block version field indicates the structure's presence. +# As of 8/2011, the Original Bitcoin Client does not use it. CAuxPow +# originated in Namecoin; see +# https://github.com/vinced/namecoin/blob/mergedmine/doc/README_merged-mining.md. +def parse_AuxPow(vds): + d = parse_MerkleTx(vds) + n_chainMerkleBranch = vds.read_compact_size() + d['chainMerkleBranch'] = vds.read_bytes(32*n_chainMerkleBranch) + d['chainIndex'] = vds.read_int32() + d['parentBlock'] = parse_BlockHeader(vds) + return d + +def parse_BlockHeader(vds): + d = {} + header_start = vds.read_cursor + d['version'] = vds.read_int32() + d['hashPrev'] = vds.read_bytes(32) + d['hashMerkleRoot'] = vds.read_bytes(32) + d['nTime'] = vds.read_uint32() + d['nBits'] = vds.read_uint32() + d['nNonce'] = vds.read_uint32() + header_end = vds.read_cursor + d['__header__'] = vds.input[header_start:header_end] + return d + +def parse_Block(vds): + d = parse_BlockHeader(vds) + if d['version'] & (1 << 8): + d['auxpow'] = parse_AuxPow(vds) + d['transactions'] = [] + nTransactions = vds.read_compact_size() + for i in xrange(nTransactions): + d['transactions'].append(parse_Transaction(vds)) + + return d + +def deserialize_Block(d): + result = "Time: "+time.ctime(d['nTime'])+" Nonce: "+str(d['nNonce']) + result += "\nnBits: 0x"+hex(d['nBits']) + result += "\nhashMerkleRoot: 0x"+d['hashMerkleRoot'][::-1].encode('hex_codec') + result += "\nPrevious block: "+d['hashPrev'][::-1].encode('hex_codec') + result += "\n%d transactions:\n"%len(d['transactions']) + for t in d['transactions']: + result += deserialize_Transaction(t)+"\n" + result += "\nRaw block header: "+d['__header__'].encode('hex_codec') + return result + +def parse_BlockLocator(vds): + d = { 'hashes' : [] } + nHashes = vds.read_compact_size() + for i in xrange(nHashes): + d['hashes'].append(vds.read_bytes(32)) + return d + +def deserialize_BlockLocator(d): + result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') + return result + +opcodes = Enumeration("Opcodes", [ + ("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED", + "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7", + "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16", + "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY", + "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP", + "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT", + "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", "OP_AND", + "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL", + "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV", + "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR", + "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN", + "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX", + "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", + "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", + "OP_CHECKMULTISIGVERIFY", + ("OP_SINGLEBYTE_END", 0xF0), + ("OP_DOUBLEBYTE_BEGIN", 0xF000), + "OP_PUBKEY", "OP_PUBKEYHASH", + ("OP_INVALIDOPCODE", 0xFFFF), +]) + +def script_GetOp(bytes): + i = 0 + while i < len(bytes): + vch = None + opcode = ord(bytes[i]) + i += 1 + if opcode >= opcodes.OP_SINGLEBYTE_END: + opcode <<= 8 + opcode |= bytes[i] + i += 1 + + if opcode <= opcodes.OP_PUSHDATA4: + nSize = opcode + if opcode == opcodes.OP_PUSHDATA1: + nSize = ord(bytes[i]) + i += 1 + elif opcode == opcodes.OP_PUSHDATA2: + nSize = unpack_from(' 0: result += " " + if opcode <= opcodes.OP_PUSHDATA4: + result += "%d:"%(opcode,) + result += short_hex(vch) + else: + result += script_GetOpName(opcode) + return result + +def match_decoded(decoded, to_match): + if len(decoded) != len(to_match): + return False; + for i in range(len(decoded)): + if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4: + continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent. + if to_match[i] != decoded[i][0]: + return False + return True + +def extract_public_key(bytes): + decoded = [ x for x in script_GetOp(bytes) ] + + # non-generated TxIn transactions push a signature + # (seventy-something bytes) and then their public key + # (65 bytes) onto the stack: + match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] + if match_decoded(decoded, match): + return public_key_to_bc_address(decoded[1][1]) + + # The Genesis Block, self-payments, and pay-by-IP-address payments look like: + # 65 BYTES:... CHECKSIG + match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ] + if match_decoded(decoded, match): + return public_key_to_bc_address(decoded[0][1]) + + # Pay-by-Bitcoin-address TxOuts look like: + # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG + match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] + if match_decoded(decoded, match): + return hash_160_to_bc_address(decoded[2][1]) + + return "(None)" diff --git a/bitcointools/enumeration.py b/bitcointools/enumeration.py new file mode 100644 index 0000000..d3d2612 --- /dev/null +++ b/bitcointools/enumeration.py @@ -0,0 +1,41 @@ +# +# enum-like type +# From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/ +# +import types, string, exceptions + +class EnumException(exceptions.Exception): + pass + +class Enumeration: + def __init__(self, name, enumList): + self.__doc__ = name + lookup = { } + reverseLookup = { } + i = 0 + uniqueNames = [ ] + uniqueValues = [ ] + for x in enumList: + if type(x) == types.TupleType: + x, i = x + if type(x) != types.StringType: + raise EnumException, "enum name is not a string: " + x + if type(i) != types.IntType: + raise EnumException, "enum value is not an integer: " + i + if x in uniqueNames: + raise EnumException, "enum name is not unique: " + x + if i in uniqueValues: + raise EnumException, "enum value is not unique for " + x + uniqueNames.append(x) + uniqueValues.append(i) + lookup[x] = i + reverseLookup[i] = x + i = i + 1 + self.lookup = lookup + self.reverseLookup = reverseLookup + def __getattr__(self, attr): + if not self.lookup.has_key(attr): + raise AttributeError + return self.lookup[attr] + def whatis(self, value): + return self.reverseLookup[value] diff --git a/bitcointools/fixwallet.py b/bitcointools/fixwallet.py new file mode 100755 index 0000000..dda57a4 --- /dev/null +++ b/bitcointools/fixwallet.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# Recover from a semi-corrupt wallet +# +from bsddb.db import * +import logging +import sys + +from wallet import rewrite_wallet, trim_wallet +from util import determine_db_dir, create_env + +def main(): + import optparse + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--datadir", dest="datadir", default=None, + help="Look for files here (defaults to bitcoin default)") + parser.add_option("--out", dest="outfile", default="walletNEW.dat", + help="Name of output file (default: walletNEW.dat)") + parser.add_option("--clean", action="store_true", dest="clean", default=False, + help="Clean out old, spent change addresses and transactions") + parser.add_option("--skipkey", dest="skipkey", + help="Skip entries with keys that contain given string") + parser.add_option("--tweakspent", dest="tweakspent", + help="Tweak transaction to mark unspent") + (options, args) = parser.parse_args() + + if options.datadir is None: + db_dir = determine_db_dir() + else: + db_dir = options.datadir + + try: + db_env = create_env(db_dir) + except DBNoSuchFileError: + logging.error("Couldn't open " + db_dir) + sys.exit(1) + + if options.clean: + trim_wallet(db_env, options.outfile) + + elif options.skipkey: + def pre_put_callback(type, data): + if options.skipkey in data['__key__']: + return False + return True + rewrite_wallet(db_env, options.outfile, pre_put_callback) + elif options.tweakspent: + txid = options.tweakspent.decode('hex_codec')[::-1] + def tweak_spent_callback(type, data): + if txid in data['__key__']: + data['__value__'] = data['__value__'][:-1]+'\0' + return True + rewrite_wallet(db_env, options.outfile, tweak_spent_callback) + pass + else: + rewrite_wallet(db_env, options.outfile) + + db_env.close() + +if __name__ == '__main__': + main() diff --git a/bitcointools/jsonToCSV.py b/bitcointools/jsonToCSV.py new file mode 100755 index 0000000..a41ee84 --- /dev/null +++ b/bitcointools/jsonToCSV.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# +# Reads an array of JSON objects and writes out CSV-format, +# with key names in first row. +# Columns will be union of all keys in the objects. +# + +import csv +import json +import sys + +json_string = sys.stdin.read() +json_array = json.loads(json_string) + +columns = set() +for item in json_array: + columns.update(set(item)) + +writer = csv.writer(sys.stdout) +writer.writerow(list(columns)) +for item in json_array: + row = [] + for c in columns: + if c in item: row.append(str(item[c])) + else: row.append('') + writer.writerow(row) + diff --git a/bitcointools/statistics.py b/bitcointools/statistics.py new file mode 100755 index 0000000..fd2c01f --- /dev/null +++ b/bitcointools/statistics.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# Read the block database, generate monthly statistics and dump out +# a CSV file. +# +from bsddb.db import * +from datetime import date +import logging +import os +import sys + +from BCDataStream import * +from block import scan_blocks +from collections import defaultdict +from deserialize import parse_Block +from util import determine_db_dir, create_env + +def main(): + import optparse + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--datadir", dest="datadir", default=None, + help="Look for files here (defaults to bitcoin default)") + (options, args) = parser.parse_args() + + if options.datadir is None: + db_dir = determine_db_dir() + else: + db_dir = options.datadir + + try: + db_env = create_env(db_dir) + except DBNoSuchFileError: + logging.error("Couldn't open " + db_dir) + sys.exit(1) + + blockfile = open(os.path.join(db_dir, "blk%04d.dat"%(1,)), "rb") + block_datastream = BCDataStream() + block_datastream.map_file(blockfile, 0) + + n_transactions = defaultdict(int) + v_transactions = defaultdict(float) + v_transactions_min = defaultdict(float) + v_transactions_max = defaultdict(float) + def gather_stats(block_data): + block_datastream.seek_file(block_data['nBlockPos']) + data = parse_Block(block_datastream) + block_date = date.fromtimestamp(data['nTime']) + key = "%d-%02d"%(block_date.year, block_date.month) + for txn in data['transactions'][1:]: + values = [] + for txout in txn['txOut']: + n_transactions[key] += 1 + v_transactions[key] += txout['value'] + values.append(txout['value']) + v_transactions_min[key] += min(values) + v_transactions_max[key] += max(values) + return True + + scan_blocks(db_dir, db_env, gather_stats) + + db_env.close() + + keys = n_transactions.keys() + keys.sort() + for k in keys: + v = v_transactions[k]/1.0e8 + v_min = v_transactions_min[k]/1.0e8 + v_max = v_transactions_max[k]/1.0e8 + # Columns are: + # month n_transactions min max total + # ... where min and max add up just the smallest or largest + # output in each transaction; the true value of bitcoins + # transferred will be somewhere between min and max. + # We don't know how many are transfers-to-self, though, and + # this will undercount multi-txout-transactions (which is good + # right now, because they're mostly used for mining pool + # payouts that arguably shouldn't count). + print "%s,%d,%.2f,%.2f,%.2f"%(k, n_transactions[k], v_min, v_max, v) + +if __name__ == '__main__': + main() diff --git a/bitcointools/testBCDataStream.py b/bitcointools/testBCDataStream.py new file mode 100644 index 0000000..5452340 --- /dev/null +++ b/bitcointools/testBCDataStream.py @@ -0,0 +1,26 @@ +# +# Unit tests for BCDataStream class +# + +import unittest + +import BCDataStream + +class Tests(unittest.TestCase): + def setUp(self): + self.ds = BCDataStream.BCDataStream() + + def testString(self): + t = { + "\x07setting" : "setting", + "\xfd\x00\x07setting" : "setting", + "\xfe\x00\x00\x00\x07setting" : "setting", + } + for (input, output) in t.iteritems(): + self.ds.clear() + self.ds.write(input) + got = self.ds.read_string() + self.assertEqual(output, got) + +if __name__ == "__main__": + unittest.main() diff --git a/bitcointools/transaction.py b/bitcointools/transaction.py new file mode 100644 index 0000000..962c8e9 --- /dev/null +++ b/bitcointools/transaction.py @@ -0,0 +1,70 @@ +# +# Code for dumping a single transaction, given its ID +# + +from bsddb.db import * +import logging +import os.path +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address +from util import short_hex +from deserialize import * + +def _read_CDiskTxPos(stream): + n_file = stream.read_uint32() + n_block_pos = stream.read_uint32() + n_tx_pos = stream.read_uint32() + return (n_file, n_block_pos, n_tx_pos) + +def _dump_tx(datadir, tx_hash, tx_pos): + blockfile = open(os.path.join(datadir, "blk%04d.dat"%(tx_pos[0],)), "rb") + ds = BCDataStream() + ds.map_file(blockfile, tx_pos[2]) + d = parse_Transaction(ds) + print deserialize_Transaction(d) + ds.close_file() + blockfile.close() + +def dump_transaction(datadir, db_env, tx_id): + """ Dump a transaction, given hexadecimal tx_id-- either the full ID + OR a short_hex version of the id. + """ + db = DB(db_env) + try: + r = db.open("blkindex.dat", "main", DB_BTREE, DB_THREAD|DB_RDONLY) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open blkindex.dat/main. Try quitting any running Bitcoin apps.") + sys.exit(1) + + kds = BCDataStream() + vds = BCDataStream() + + n_tx = 0 + n_blockindex = 0 + + key_prefix = "\x02tx"+(tx_id[-4:].decode('hex_codec')[::-1]) + cursor = db.cursor() + (key, value) = cursor.set_range(key_prefix) + + while key.startswith(key_prefix): + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + hash256 = (kds.read_bytes(32)) + hash_hex = long_hex(hash256[::-1]) + version = vds.read_uint32() + tx_pos = _read_CDiskTxPos(vds) + if (hash_hex.startswith(tx_id) or short_hex(hash256[::-1]).startswith(tx_id)): + _dump_tx(datadir, hash256, tx_pos) + + (key, value) = cursor.next() + + db.close() + diff --git a/bitcointools/util.py b/bitcointools/util.py new file mode 100644 index 0000000..4f0adfd --- /dev/null +++ b/bitcointools/util.py @@ -0,0 +1,33 @@ +# +# Misc util routines +# + +from bsddb.db import * + +def long_hex(bytes): + return bytes.encode('hex_codec') + +def short_hex(bytes): + t = bytes.encode('hex_codec') + if len(t) < 11: + return t + return t[0:4]+"..."+t[-4:] + +def determine_db_dir(): + import os + import os.path + import platform + if platform.system() == "Darwin": + return os.path.expanduser("~/Library/Application Support/Bitcoin/") + elif platform.system() == "Windows": + return os.path.join(os.environ['APPDATA'], "Bitcoin") + return os.path.expanduser("~/.bitcoin") + +def create_env(db_dir=None): + if db_dir is None: + db_dir = determine_db_dir() + db_env = DBEnv(0) + r = db_env.open(db_dir, + (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL| + DB_INIT_TXN|DB_THREAD|DB_RECOVER)) + return db_env diff --git a/bitcointools/wallet.py b/bitcointools/wallet.py new file mode 100644 index 0000000..a41d3a6 --- /dev/null +++ b/bitcointools/wallet.py @@ -0,0 +1,349 @@ +# +# Code for parsing the wallet.dat file +# + +from bsddb.db import * +import logging +import re +import sys +import time + +from BCDataStream import * +from base58 import public_key_to_bc_address, bc_address_to_hash_160, hash_160 +from util import short_hex, long_hex +from deserialize import * + +def open_wallet(db_env, writable=False): + db = DB(db_env) + flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY) + try: + r = db.open("wallet.dat", "main", DB_BTREE, flags) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.") + sys.exit(1) + + return db + +def parse_wallet(db, item_callback): + kds = BCDataStream() + vds = BCDataStream() + + for (key, value) in db.items(): + d = { } + + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + + d["__key__"] = key + d["__value__"] = value + d["__type__"] = type + + try: + if type == "tx": + d["tx_id"] = kds.read_bytes(32) + d.update(parse_WalletTx(vds)) + elif type == "name": + d['hash'] = kds.read_string() + d['name'] = vds.read_string() + elif type == "version": + d['version'] = vds.read_uint32() + elif type == "setting": + d['setting'] = kds.read_string() + d['value'] = parse_setting(d['setting'], vds) + elif type == "key": + d['public_key'] = kds.read_bytes(kds.read_compact_size()) + d['private_key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "wkey": + d['public_key'] = kds.read_bytes(kds.read_compact_size()) + d['private_key'] = vds.read_bytes(vds.read_compact_size()) + d['created'] = vds.read_int64() + d['expires'] = vds.read_int64() + d['comment'] = vds.read_string() + elif type == "ckey": + d['public_key'] = kds.read_bytes(kds.read_compact_size()) + d['crypted_key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "mkey": + d['nID'] = kds.read_int32() + d['crypted_key'] = vds.read_bytes(vds.read_compact_size()) + d['salt'] = vds.read_bytes(vds.read_compact_size()) + d['nDerivationMethod'] = vds.read_int32() + d['nDeriveIterations'] = vds.read_int32() + d['vchOtherDerivationParameters'] = vds.read_bytes(vds.read_compact_size()) + elif type == "defaultkey": + d['key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "pool": + d['n'] = kds.read_int64() + d['nVersion'] = vds.read_int32() + d['nTime'] = vds.read_int64() + d['public_key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "acc": + d['account'] = kds.read_string() + d['nVersion'] = vds.read_int32() + d['public_key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "acentry": + d['account'] = kds.read_string() + d['n'] = kds.read_uint64() + d['nVersion'] = vds.read_int32() + d['nCreditDebit'] = vds.read_int64() + d['nTime'] = vds.read_int64() + d['otherAccount'] = vds.read_string() + d['comment'] = vds.read_string() + elif type == "bestblock": + d['nVersion'] = vds.read_int32() + d.update(parse_BlockLocator(vds)) + else: + print "Unknown key type: "+type + + item_callback(type, d) + + except Exception, e: + print("ERROR parsing wallet.dat, type %s"%type) + print("key data in hex: %s"%key.encode('hex_codec')) + print("value data in hex: %s"%value.encode('hex_codec')) + +def update_wallet(db, type, data): + """Write a single item to the wallet. + db must be open with writable=True. + type and data are the type code and data dictionary as parse_wallet would + give to item_callback. + data's __key__, __value__ and __type__ are ignored; only the primary data + fields are used. + """ + d = data + kds = BCDataStream() + vds = BCDataStream() + + # Write the type code to the key + kds.write_string(type) + vds.write("") # Ensure there is something + + try: + if type == "tx": + raise NotImplementedError("Writing items of type 'tx'") + kds.write(d['tx_id']) + #d.update(parse_WalletTx(vds)) + elif type == "name": + kds.write(d['hash']) + vds.write(d['name']) + elif type == "version": + vds.write_uint32(d['version']) + elif type == "setting": + raise NotImplementedError("Writing items of type 'setting'") + kds.write_string(d['setting']) + #d['value'] = parse_setting(d['setting'], vds) + elif type == "key": + kds.write_string(d['public_key']) + vds.write_string(d['private_key']) + elif type == "wkey": + kds.write_string(d['public_key']) + vds.write_string(d['private_key']) + vds.write_int64(d['created']) + vds.write_int64(d['expires']) + vds.write_string(d['comment']) + elif type == "ckey": + kds.write_string(d['public_key']) + kds.write_string(d['crypted_key']) + elif type == "mkey": + kds.write_int32(d['nID']) + vds.write_string(d['crypted_key']) + vds.write_string(d['salt']) + vds.write_int32(d['nDeriveIterations']) + vds.write_int32(d['nDerivationMethod']) + vds.write_string(d['vchOtherDerivationParameters']) + elif type == "defaultkey": + vds.write_string(d['key']) + elif type == "pool": + kds.write_int64(d['n']) + vds.write_int32(d['nVersion']) + vds.write_int64(d['nTime']) + vds.write_string(d['public_key']) + elif type == "acc": + kds.write_string(d['account']) + vds.write_int32(d['nVersion']) + vds.write_string(d['public_key']) + elif type == "acentry": + kds.write_string(d['account']) + kds.write_uint64(d['n']) + vds.write_int32(d['nVersion']) + vds.write_int64(d['nCreditDebit']) + vds.write_int64(d['nTime']) + vds.write_string(d['otherAccount']) + vds.write_string(d['comment']) + elif type == "bestblock": + vds.write_int32(d['nVersion']) + vds.write_compact_size(len(d['hashes'])) + for h in d['hashes']: + vds.write(h) + else: + print "Unknown key type: "+type + + # Write the key/value pair to the database + db.put(kds.input, vds.input) + + except Exception, e: + print("ERROR writing to wallet.dat, type %s"%type) + print("data dictionary: %r"%data) + +def dump_wallet(db_env, print_wallet, print_wallet_transactions, transaction_filter): + db = open_wallet(db_env) + + wallet_transactions = [] + transaction_index = { } + owner_keys = { } + + def item_callback(type, d): + if type == "tx": + wallet_transactions.append( d ) + transaction_index[d['tx_id']] = d + elif type == "key": + owner_keys[public_key_to_bc_address(d['public_key'])] = d['private_key'] + elif type == "ckey": + owner_keys[public_key_to_bc_address(d['public_key'])] = d['crypted_key'] + + if not print_wallet: + return + if type == "tx": + return + elif type == "name": + print("ADDRESS "+d['hash']+" : "+d['name']) + elif type == "version": + print("Version: %d"%(d['version'],)) + elif type == "setting": + print(d['setting']+": "+str(d['value'])) + elif type == "key": + print("PubKey "+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) + + ": PriKey "+ short_hex(d['private_key'])) + elif type == "wkey": + print("WPubKey 0x"+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) + + ": WPriKey 0x"+ short_hex(d['crypted_key'])) + print(" Created: "+time.ctime(d['created'])+" Expires: "+time.ctime(d['expires'])+" Comment: "+d['comment']) + elif type == "ckey": + print("PubKey "+ short_hex(d['public_key']) + " " + public_key_to_bc_address(d['public_key']) + + ": Encrypted PriKey "+ short_hex(d['crypted_key'])) + elif type == "mkey": + print("Master Key %d"%(d['nID']) + ": 0x"+ short_hex(d['crypted_key']) + + ", Salt: 0x"+ short_hex(d['salt']) + + ". Passphrase hashed %d times with method %d with other parameters 0x"%(d['nDeriveIterations'], d['nDerivationMethod']) + + long_hex(d['vchOtherDerivationParameters'])) + elif type == "defaultkey": + print("Default Key: 0x"+ short_hex(d['key']) + " " + public_key_to_bc_address(d['key'])) + elif type == "pool": + print("Change Pool key %d: %s (Time: %s)"% (d['n'], public_key_to_bc_address(d['public_key']), time.ctime(d['nTime']))) + elif type == "acc": + print("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key']))) + elif type == "acentry": + print("Move '%s' %d (other: '%s', time: %s, entry %d) %s"% + (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])) + elif type == "bestblock": + print deserialize_BlockLocator(d) + else: + print "Unknown key type: "+type + + parse_wallet(db, item_callback) + + if print_wallet_transactions: + keyfunc = lambda i: i['timeReceived'] + for d in sorted(wallet_transactions, key=keyfunc): + tx_value = deserialize_WalletTx(d, transaction_index, owner_keys) + if len(transaction_filter) > 0 and re.search(transaction_filter, tx_value) is None: continue + + print("==WalletTransaction== "+long_hex(d['tx_id'][::-1])) + print(tx_value) + + db.close() + +def dump_accounts(db_env): + db = open_wallet(db_env) + + kds = BCDataStream() + vds = BCDataStream() + + accounts = set() + + for (key, value) in db.items(): + kds.clear(); kds.write(key) + vds.clear(); vds.write(value) + + type = kds.read_string() + + if type == "acc": + accounts.add(kds.read_string()) + elif type == "name": + accounts.add(vds.read_string()) + elif type == "acentry": + accounts.add(kds.read_string()) + # Note: don't need to add otheraccount, because moves are + # always double-entry + + for name in sorted(accounts): + print(name) + + db.close() + +def rewrite_wallet(db_env, destFileName, pre_put_callback=None): + db = open_wallet(db_env) + + db_out = DB(db_env) + try: + r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open %s."%destFileName) + sys.exit(1) + + def item_callback(type, d): + if (pre_put_callback is None or pre_put_callback(type, d)): + db_out.put(d["__key__"], d["__value__"]) + + parse_wallet(db, item_callback) + + db_out.close() + db.close() + +def trim_wallet(db_env, destFileName, pre_put_callback=None): + """Write out ONLY address book public/private keys + THIS WILL NOT WRITE OUT 'change' KEYS-- you should + send all of your bitcoins to one of your public addresses + before calling this. + """ + db = open_wallet(db_env) + + pubkeys = [] + def gather_pubkeys(type, d): + if type == "name": + pubkeys.append(bc_address_to_hash_160(d['hash'])) + + parse_wallet(db, gather_pubkeys) + + db_out = DB(db_env) + try: + r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE) + except DBError: + r = True + + if r is not None: + logging.error("Couldn't open %s."%destFileName) + sys.exit(1) + + def item_callback(type, d): + should_write = False + if type in [ 'version', 'name', 'acc' ]: + should_write = True + if type in [ 'key', 'wkey', 'ckey' ] and hash_160(d['public_key']) in pubkeys: + should_write = True + if pre_put_callback is not None: + should_write = pre_put_callback(type, d, pubkeys) + if should_write: + db_out.put(d["__key__"], d["__value__"]) + + parse_wallet(db, item_callback) + + db_out.close() + db.close() diff --git a/src/base58.h b/src/base58.h index bc681a0..0accbf9 100644 --- a/src/base58.h +++ b/src/base58.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin Developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -253,9 +254,9 @@ public: }; /** base58-encoded bitcoin addresses. - * Public-key-hash-addresses have version 0 (or 111 testnet). + * Public-key-hash-addresses have version 55 (or 111 testnet). * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. - * Script-hash-addresses have version 5 (or 196 testnet). + * Script-hash-addresses have version 57 (or 196 testnet). * The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. */ class CBitcoinAddress : public CBase58Data @@ -263,8 +264,8 @@ class CBitcoinAddress : public CBase58Data public: enum { - PUBKEY_ADDRESS = 0, - SCRIPT_ADDRESS = 5, + PUBKEY_ADDRESS = 55, // ppcoin: addresses begin with 'P' + SCRIPT_ADDRESS = 57, // ppcoin: addresses begin with 'Q' PUBKEY_ADDRESS_TEST = 111, SCRIPT_ADDRESS_TEST = 196, }; diff --git a/src/bignum.h b/src/bignum.h index 307017b..f09c4aa 100644 --- a/src/bignum.h +++ b/src/bignum.h @@ -184,6 +184,21 @@ public: BN_mpi2bn(pch, p - pch, this); } + uint64 getuint64() + { + unsigned int nSize = BN_bn2mpi(this, NULL); + if (nSize < 4) + return 0; + std::vector vch(nSize); + BN_bn2mpi(this, &vch[0]); + if (vch.size() > 4) + vch[4] &= 0x7f; + uint64 n = 0; + for (int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--) + ((unsigned char*)&n)[i] = vch[j]; + return n; + } + void setuint256(uint256 n) { unsigned char pch[sizeof(n) + 6]; diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 3553f81..08a1dac 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,6 +10,7 @@ #include "walletdb.h" #include "net.h" #include "init.h" +#include "checkpoints.h" #include "ui_interface.h" #include "bitcoinrpc.h" @@ -61,7 +63,7 @@ double GetDifficulty(const CBlockIndex* blockindex = NULL) if (pindexBest == NULL) return 1.0; else - blockindex = pindexBest; + blockindex = GetLastBlockIndex(pindexBest, false); } int nShift = (blockindex->nBits >> 24) & 0xff; @@ -87,7 +89,7 @@ double GetDifficulty(const CBlockIndex* blockindex = NULL) int64 AmountFromValue(const Value& value) { double dAmount = value.get_real(); - if (dAmount <= 0.0 || dAmount > 21000000.0) + if (dAmount <= 0.0 || dAmount > MAX_MONEY) throw JSONRPCError(-3, "Invalid amount"); int64 nAmount = roundint64(dAmount * COIN); if (!MoneyRange(nAmount)) @@ -223,10 +225,10 @@ Value stop(const Array& params, bool fHelp) if (fHelp || params.size() != 0) throw runtime_error( "stop\n" - "Stop bitcoin server."); + "Stop ppcoin server."); // Shutdown will take long enough that the response should get back StartShutdown(); - return "bitcoin server stopping"; + return "ppcoin server stopping"; } @@ -333,13 +335,16 @@ Value getinfo(const Array& params, bool fHelp) "Returns an object containing various state info."); Object obj; - obj.push_back(Pair("version", (int)CLIENT_VERSION)); + obj.push_back(Pair("version", FormatFullVersion())); obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION)); obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); + obj.push_back(Pair("newmint", ValueFromAmount(pwalletMain->GetNewMint()))); + obj.push_back(Pair("stake", ValueFromAmount(pwalletMain->GetStake()))); obj.push_back(Pair("blocks", (int)nBestHeight)); obj.push_back(Pair("connections", (int)vNodes.size())); obj.push_back(Pair("proxy", (fUseProxy ? addrProxy.ToStringIPPort() : string()))); + obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP())); obj.push_back(Pair("difficulty", (double)GetDifficulty())); obj.push_back(Pair("testnet", fTestNet)); obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime())); @@ -379,7 +384,7 @@ Value getnewaddress(const Array& params, bool fHelp) if (fHelp || params.size() > 1) throw runtime_error( "getnewaddress [account]\n" - "Returns a new bitcoin address for receiving payments. " + "Returns a new ppcoin address for receiving payments. " "If [account] is specified (recommended), it is added to the address book " "so payments received with the address will be credited to [account]."); @@ -446,7 +451,7 @@ Value getaccountaddress(const Array& params, bool fHelp) if (fHelp || params.size() != 1) throw runtime_error( "getaccountaddress \n" - "Returns the current bitcoin address for receiving payments to this account."); + "Returns the current ppcoin address for receiving payments to this account."); // Parse the account first so we don't generate a key if there's an error string strAccount = AccountFromValue(params[0]); @@ -464,12 +469,12 @@ Value setaccount(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "setaccount \n" + "setaccount \n" "Sets the account associated with the given address."); CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid ppcoin address"); string strAccount; @@ -494,12 +499,12 @@ Value getaccount(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( - "getaccount \n" + "getaccount \n" "Returns the account associated with the given address."); CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid ppcoin address"); string strAccount; map::iterator mi = pwalletMain->mapAddressBook.find(address); @@ -532,17 +537,14 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) Value settxfee(const Array& params, bool fHelp) { - if (fHelp || params.size() < 1 || params.size() > 1) + if (fHelp || params.size() < 1 || params.size() > 1 || AmountFromValue(params[0]) < MIN_TX_FEE) throw runtime_error( "settxfee \n" - " is a real and is rounded to the nearest 0.00000001"); + " is a real and is rounded to 0.01 (cent)\n" + "Minimum and default transaction fee per KB is 1 cent"); - // Amount - int64 nAmount = 0; - if (params[0].get_real() != 0.0) - nAmount = AmountFromValue(params[0]); // rejects 0.0 amounts - - nTransactionFee = nAmount; + nTransactionFee = AmountFromValue(params[0]); + nTransactionFee = (nTransactionFee / CENT) * CENT; // round to cent return true; } @@ -550,17 +552,17 @@ Value sendtoaddress(const Array& params, bool fHelp) { if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) throw runtime_error( - "sendtoaddress [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.00000001\n" + "sendtoaddress [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001\n" "requires wallet passphrase to be set with walletpassphrase first"); if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) throw runtime_error( - "sendtoaddress [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.00000001"); + "sendtoaddress [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001"); CBitcoinAddress address(params[0].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid ppcoin address"); // Amount int64 nAmount = AmountFromValue(params[1]); @@ -586,7 +588,7 @@ Value signmessage(const Array& params, bool fHelp) { if (fHelp || params.size() != 2) throw runtime_error( - "signmessage \n" + "signmessage \n" "Sign a message with the private key of an address"); if (pwalletMain->IsLocked()) @@ -618,7 +620,7 @@ Value verifymessage(const Array& params, bool fHelp) { if (fHelp || params.size() != 3) throw runtime_error( - "verifymessage \n" + "verifymessage \n" "Verify a signed message"); string strAddress = params[0].get_str(); @@ -651,14 +653,14 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "getreceivedbyaddress [minconf=1]\n" - "Returns the total amount received by in transactions with at least [minconf] confirmations."); + "getreceivedbyaddress [minconf=1]\n" + "Returns the total amount received by in transactions with at least [minconf] confirmations."); // Bitcoin address CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); CScript scriptPubKey; if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid ppcoin address"); scriptPubKey.SetBitcoinAddress(address); if (!IsMine(*pwalletMain,scriptPubKey)) return (double)0.0; @@ -673,7 +675,7 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || !wtx.IsFinal()) + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) continue; BOOST_FOREACH(const CTxOut& txout, wtx.vout) @@ -720,7 +722,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || !wtx.IsFinal()) + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) continue; BOOST_FOREACH(const CTxOut& txout, wtx.vout) @@ -873,18 +875,18 @@ Value sendfrom(const Array& params, bool fHelp) { if (pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) throw runtime_error( - "sendfrom [minconf=1] [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.00000001\n" + "sendfrom [minconf=1] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001\n" "requires wallet passphrase to be set with walletpassphrase first"); if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) throw runtime_error( - "sendfrom [minconf=1] [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.00000001"); + "sendfrom [minconf=1] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001"); string strAccount = AccountFromValue(params[0]); CBitcoinAddress address(params[1].get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid bitcoin address"); + throw JSONRPCError(-5, "Invalid ppcoin address"); int64 nAmount = AmountFromValue(params[2]); int nMinDepth = 1; if (params.size() > 3) @@ -945,7 +947,7 @@ Value sendmany(const Array& params, bool fHelp) { CBitcoinAddress address(s.name_); if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid bitcoin address:")+s.name_); + throw JSONRPCError(-5, string("Invalid ppcoin address:")+s.name_); if (setAddress.count(address)) throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); @@ -961,6 +963,8 @@ Value sendmany(const Array& params, bool fHelp) if (pwalletMain->IsLocked()) throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + if (fWalletUnlockStakeOnly) + throw JSONRPCError(-13, "Error: Wallet unlocked for coinstake only."); // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); @@ -1086,7 +1090,7 @@ Value ListReceived(const Array& params, bool fByAccounts) { const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || !wtx.IsFinal()) + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) continue; int nDepth = wtx.GetDepthInMainChain(); @@ -1586,17 +1590,18 @@ void ThreadCleanWalletPassphrase(void* parg) Value walletpassphrase(const Array& params, bool fHelp) { - if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) + if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 3)) throw runtime_error( - "walletpassphrase \n" - "Stores the wallet decryption key in memory for seconds."); + "walletpassphrase [stakeonly]\n" + "Stores the wallet decryption key in memory for seconds.\n" + "stakeonly is optional true/false allowing only stake creation."); if (fHelp) return true; if (!pwalletMain->IsCrypted()) throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called."); if (!pwalletMain->IsLocked()) - throw JSONRPCError(-17, "Error: Wallet is already unlocked."); + throw JSONRPCError(-17, "Error: Wallet is already unlocked, use walletlock first if need to change unlock settings."); // Note that the walletpassphrase is stored in params[0] which is not mlock()ed SecureString strWalletPass; @@ -1619,6 +1624,12 @@ Value walletpassphrase(const Array& params, bool fHelp) int64* pnSleepTime = new int64(params[1].get_int64()); CreateThread(ThreadCleanWalletPassphrase, pnSleepTime); + // ppcoin: if user OS account compromised prevent trivial sendmoney commands + if (params.size() > 2) + fWalletUnlockStakeOnly = params[2].get_bool(); + else + fWalletUnlockStakeOnly = false; + return Value::null; } @@ -1708,7 +1719,7 @@ Value encryptwallet(const Array& params, bool fHelp) // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: StartShutdown(); - return "wallet encrypted; bitcoin server stopping, restart to run with encrypted wallet"; + return "wallet encrypted; ppcoin server stopping, restart to run with encrypted wallet"; } @@ -1716,8 +1727,8 @@ Value validateaddress(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( - "validateaddress \n" - "Return information about ."); + "validateaddress \n" + "Return information about ."); CBitcoinAddress address(params[0].get_str()); bool isValid = address.IsValid(); @@ -1779,10 +1790,10 @@ Value getwork(const Array& params, bool fHelp) "If [data] is specified, tries to solve the block and returns true if it was successful."); if (vNodes.empty()) - throw JSONRPCError(-9, "Bitcoin is not connected!"); + throw JSONRPCError(-9, "PPCoin is not connected!"); if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "Bitcoin is downloading blocks..."); + throw JSONRPCError(-10, "PPCoin is downloading blocks..."); typedef map > mapNewBlock_t; static mapNewBlock_t mapNewBlock; @@ -1812,7 +1823,7 @@ Value getwork(const Array& params, bool fHelp) nStart = GetTime(); // Create new block - pblock = CreateNewBlock(reservekey); + pblock = CreateNewBlock(pwalletMain, true); if (!pblock) throw JSONRPCError(-7, "Out of memory"); vNewBlock.push_back(pblock); @@ -1865,6 +1876,8 @@ Value getwork(const Array& params, bool fHelp) pblock->nNonce = pdata->nNonce; pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + if (!pblock->SignBlock(*pwalletMain)) + throw JSONRPCError(-100, "Unable to sign block"); return CheckWork(pblock, *pwalletMain, reservekey); } @@ -1891,10 +1904,10 @@ Value getmemorypool(const Array& params, bool fHelp) if (params.size() == 0) { if (vNodes.empty()) - throw JSONRPCError(-9, "Bitcoin is not connected!"); + throw JSONRPCError(-9, "PPCoin is not connected!"); if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "Bitcoin is downloading blocks..."); + throw JSONRPCError(-10, "PPCoin is downloading blocks..."); static CReserveKey reservekey(pwalletMain); @@ -1913,7 +1926,7 @@ Value getmemorypool(const Array& params, bool fHelp) // Create new block if(pblock) delete pblock; - pblock = CreateNewBlock(reservekey); + pblock = CreateNewBlock(pwalletMain); if (!pblock) throw JSONRPCError(-7, "Out of memory"); } @@ -1924,7 +1937,7 @@ Value getmemorypool(const Array& params, bool fHelp) Array transactions; BOOST_FOREACH(CTransaction tx, pblock->vtx) { - if(tx.IsCoinBase()) + if(tx.IsCoinBase() || tx.IsCoinStake()) continue; CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -1996,13 +2009,265 @@ Value getblock(const Array& params, bool fHelp) } +// ppcoin: get information of sync-checkpoint +Value getcheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getcheckpoint\n" + "Show info of synchronized checkpoint.\n"); + + Object result; + CBlockIndex* pindexCheckpoint; + + result.push_back(Pair("synccheckpoint", Checkpoints::hashSyncCheckpoint.ToString().c_str())); + pindexCheckpoint = mapBlockIndex[Checkpoints::hashSyncCheckpoint]; + result.push_back(Pair("height", pindexCheckpoint->nHeight)); + result.push_back(Pair("timestamp", DateTimeStrFormat("%x %H:%M:%S", pindexCheckpoint->GetBlockTime()).c_str())); + + return result; +} + + +// ppcoin: reserve balance from being staked for network protection +Value reservebalance(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "reservebalance [ [amount]]\n" + " is true or false to turn balance reserve on or off.\n" + " is a real and rounded to cent.\n" + "Set reserve amount not participating in network protection.\n" + "If no parameters provided current setting is printed.\n"); + + if (params.size() > 0) + { + bool fReserve = params[0].get_bool(); + if (fReserve) + { + if (params.size() == 1) + throw runtime_error("must provide amount to reserve balance.\n"); + int64 nAmount = AmountFromValue(params[1]); + nAmount = (nAmount / CENT) * CENT; // round to cent + if (nAmount < 0) + throw runtime_error("amount cannot be negative.\n"); + // TODO: handle persistence of nBalanceReserve + // settings removed since bitcoin 0.6 + // WriteSetting("nBalanceReserve", nBalanceReserve = nAmount); + nBalanceReserve = nAmount; + } + else + { + if (params.size() > 1) + throw runtime_error("cannot specify amount to turn off reserve.\n"); + // TODO: handle persistence of nBalanceReserve + // settings removed since bitcoin 0.6 + // WriteSetting("nBalanceReserve", nBalanceReserve = 0); + nBalanceReserve = 0; + } + } + + Object result; + result.push_back(Pair("reserve", (nBalanceReserve > 0))); + result.push_back(Pair("amount", ValueFromAmount(nBalanceReserve))); + return result; +} + + +// ppcoin: check wallet integrity +Value checkwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "checkwallet\n" + "Check wallet for integrity.\n"); + + int nMismatchSpent; + int64 nBalanceInQuestion; + if (!pwalletMain->CheckSpentCoins(nMismatchSpent, nBalanceInQuestion)) + { + Object result; + result.push_back(Pair("mismatched spent coins", nMismatchSpent)); + result.push_back(Pair("amount in question", ValueFromAmount(nBalanceInQuestion))); + return result; + } + return Value::null; +} + + +// ppcoin: repair wallet +Value repairwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "repairwallet\n" + "Repair wallet if checkwallet reports any problem.\n"); + + int nMismatchSpent; + int64 nBalanceInQuestion; + pwalletMain->FixSpentCoins(nMismatchSpent, nBalanceInQuestion); + Object result; + if (nMismatchSpent == 0) + { + result.push_back(Pair("wallet check passed", true)); + } + else + { + result.push_back(Pair("mismatched spent coins", nMismatchSpent)); + result.push_back(Pair("amount affected by repair", ValueFromAmount(nBalanceInQuestion))); + } + return result; +} + +// ppcoin: make a public-private key pair +Value makekeypair(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "makekeypair [prefix]\n" + "Make a public/private key pair.\n" + "[prefix] is optional preferred prefix for the public key.\n"); + + string strPrefix = ""; + if (params.size() > 0) + strPrefix = params[0].get_str(); + + CKey key; + int nCount = 0; + do + { + key.MakeNewKey(false); + nCount++; + } while (nCount < 10000 && strPrefix != HexStr(key.GetPubKey()).substr(0, strPrefix.size())); + + if (strPrefix != HexStr(key.GetPubKey()).substr(0, strPrefix.size())) + return Value::null; + + CPrivKey vchPrivKey = key.GetPrivKey(); + Object result; + result.push_back(Pair("PrivateKey", HexStr(vchPrivKey.begin(), vchPrivKey.end()))); + result.push_back(Pair("PublicKey", HexStr(key.GetPubKey()))); + return result; +} + +extern CCriticalSection cs_mapAlerts; +extern map mapAlerts; + +// ppcoin: send alert. +// There is a known deadlock situation with ThreadMessageHandler +// ThreadMessageHandler: holds cs_vSend and acquiring cs_main in SendMessages() +// ThreadRPCServer: holds cs_main and acquiring cs_vSend in alert.RelayTo()/PushMessage()/BeginMessage() +Value sendalert(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 5) + throw runtime_error( + "sendalert [cancelupto]\n" + " is the alert text message\n" + " is hex string of alert master private key\n" + " is the minimum applicable client version\n" + " is the maximum applicable client version\n" + " is the alert id\n" + "[cancelupto] cancels all alert id's up to this number\n" + "Returns true or false."); + + CAlert alert; + CKey key; + + alert.strStatusBar = params[0].get_str(); + alert.nMinVer = params[2].get_int(); + alert.nMaxVer = params[3].get_int(); + alert.nID = params[4].get_int(); + if (params.size() > 5) + alert.nCancel = params[5].get_int(); + alert.nVersion = PROTOCOL_VERSION; + alert.nRelayUntil = GetAdjustedTime() + 365*24*60*60; + alert.nExpiration = GetAdjustedTime() + 365*24*60*60; + alert.nPriority = 1; + + CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); + sMsg << (CUnsignedAlert)alert; + alert.vchMsg = vector(sMsg.begin(), sMsg.end()); + + vector vchPrivKey = ParseHex(params[1].get_str()); + key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash + if (!key.Sign(Hash(alert.vchMsg.begin(), alert.vchMsg.end()), alert.vchSig)) + throw runtime_error( + "Unable to sign alert, check private key?\n"); + if(!alert.ProcessAlert()) + throw runtime_error( + "Failed to process alert.\n"); + // Relay alert + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + alert.RelayTo(pnode); + } + Object result; + result.push_back(Pair("strStatusBar", alert.strStatusBar)); + result.push_back(Pair("nVersion", alert.nVersion)); + result.push_back(Pair("nMinVer", alert.nMinVer)); + result.push_back(Pair("nMaxVer", alert.nMaxVer)); + result.push_back(Pair("nID", alert.nID)); + if (alert.nCancel > 0) + result.push_back(Pair("nCancel", alert.nCancel)); + return result; +} + +// ppcoin: send checkpoint +Value sendcheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2 || params.size() < 1 ) + throw runtime_error( + "sendcheckpoint [checkpointhash]\n" + " is hex string of checkpoint master private key\n" + " is the hash of checkpoint block\n"); + CSyncCheckpoint checkpoint; + CKey key; + // TODO: omit checkpointhash parameter + if (params.size() > 1) + { + checkpoint.hashCheckpoint = uint256(params[1].get_str()); + if (!mapBlockIndex.count(checkpoint.hashCheckpoint)) + throw runtime_error( + "Provided checkpoint block is not on main chain\n"); + } + else + { + checkpoint.hashCheckpoint = Checkpoints::AutoSelectSyncCheckpoint(); + if (checkpoint.hashCheckpoint == Checkpoints::hashSyncCheckpoint) + throw runtime_error( + "Unable to select a more recent sync-checkpoint"); + } + CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); + sMsg << (CUnsignedSyncCheckpoint)checkpoint; + checkpoint.vchMsg = vector(sMsg.begin(), sMsg.end()); + vector vchPrivKey = ParseHex(params[0].get_str()); + key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash + if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig)) + throw runtime_error( + "Unable to sign checkpoint, check private key?\n"); + if(!checkpoint.ProcessSyncCheckpoint(NULL)) + throw runtime_error( + "Failed to process checkpoint.\n"); + // Relay checkpoint + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + Object result; + result.push_back(Pair("checkpoint", Checkpoints::hashSyncCheckpoint.ToString().c_str())); + result.push_back(Pair("height", mapBlockIndex[Checkpoints::hashSyncCheckpoint]->nHeight)); + result.push_back(Pair("timestamp", DateTimeStrFormat("%x %H:%M:%S", mapBlockIndex[Checkpoints::hashSyncCheckpoint]->GetBlockTime()).c_str())); + return result; +} // @@ -2059,6 +2324,13 @@ static const CRPCCommand vRPCCommands[] = { "listsinceblock", &listsinceblock, false }, { "dumpprivkey", &dumpprivkey, false }, { "importprivkey", &importprivkey, false }, + { "getcheckpoint", &getcheckpoint, true }, + { "reservebalance", &reservebalance, false}, + { "checkwallet", &checkwallet, false}, + { "repairwallet", &repairwallet, false}, + { "makekeypair", &makekeypair, false}, + { "sendalert", &sendalert, false}, + { "sendcheckpoint", &sendcheckpoint, false}, }; CRPCTable::CRPCTable() @@ -2092,7 +2364,7 @@ string HTTPPost(const string& strMsg, const map& mapRequestHeader { ostringstream s; s << "POST / HTTP/1.1\r\n" - << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" + << "User-Agent: ppcoin-json-rpc/" << FormatFullVersion() << "\r\n" << "Host: 127.0.0.1\r\n" << "Content-Type: application/json\r\n" << "Content-Length: " << strMsg.size() << "\r\n" @@ -2123,7 +2395,7 @@ static string HTTPReply(int nStatus, const string& strMsg) if (nStatus == 401) return strprintf("HTTP/1.0 401 Authorization Required\r\n" "Date: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" + "Server: ppcoin-json-rpc/%s\r\n" "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" "Content-Type: text/html\r\n" "Content-Length: 296\r\n" @@ -2150,7 +2422,7 @@ static string HTTPReply(int nStatus, const string& strMsg) "Connection: close\r\n" "Content-Length: %d\r\n" "Content-Type: application/json\r\n" - "Server: bitcoin-json-rpc/%s\r\n" + "Server: ppcoin-json-rpc/%s\r\n" "\r\n" "%s", nStatus, @@ -2364,7 +2636,7 @@ void ThreadRPCServer2(void* parg) { unsigned char rand_pwd[32]; RAND_bytes(rand_pwd, 32); - string strWhatAmI = "To use bitcoind"; + string strWhatAmI = "To use ppcoind"; if (mapArgs.count("-server")) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) @@ -2388,7 +2660,7 @@ void ThreadRPCServer2(void* parg) asio::ip::address bindAddress = mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback(); asio::io_service io_service; - ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332)); + ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", RPC_PORT)); ip::tcp::acceptor acceptor(io_service); try { @@ -2568,7 +2840,7 @@ Object CallRPC(const string& strMethod, const Array& params) SSLStream sslStream(io_service, context); SSLIOStreamDevice d(sslStream, fUseSSL); iostreams::stream stream(d); - if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332"))) + if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", CBigNum(RPC_PORT).ToString().c_str()))) throw runtime_error("couldn't connect to server"); // HTTP basic authentication @@ -2670,7 +2942,12 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "listtransactions" && n > 2) ConvertTo(params[2]); if (strMethod == "listaccounts" && n > 0) ConvertTo(params[0]); if (strMethod == "walletpassphrase" && n > 1) ConvertTo(params[1]); + if (strMethod == "walletpassphrase" && n > 2) ConvertTo(params[2]); if (strMethod == "listsinceblock" && n > 1) ConvertTo(params[1]); + if (strMethod == "sendalert" && n > 2) ConvertTo(params[2]); + if (strMethod == "sendalert" && n > 3) ConvertTo(params[3]); + if (strMethod == "sendalert" && n > 4) ConvertTo(params[4]); + if (strMethod == "sendalert" && n > 5) ConvertTo(params[5]); if (strMethod == "sendmany" && n > 1) { string s = params[1].get_str(); @@ -2680,6 +2957,8 @@ int CommandLineRPC(int argc, char *argv[]) params[1] = v.get_obj(); } if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); + if (strMethod == "reservebalance" && n > 0) ConvertTo(params[0]); + if (strMethod == "reservebalance" && n > 1) ConvertTo(params[1]); if (strMethod == "addmultisigaddress" && n > 0) ConvertTo(params[0]); if (strMethod == "addmultisigaddress" && n > 1) { diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 67e2c4c..abd72d4 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -1,4 +1,5 @@ // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,12 +8,13 @@ #include "checkpoints.h" +#include "db.h" #include "main.h" #include "uint256.h" namespace Checkpoints { - typedef std::map MapCheckpoints; + typedef std::map MapCheckpoints; // hardened checkpoints // // What makes a good checkpoint block? @@ -23,16 +25,10 @@ namespace Checkpoints // static MapCheckpoints mapCheckpoints = boost::assign::map_list_of - ( 11111, uint256("0x0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")) - ( 33333, uint256("0x000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")) - ( 74000, uint256("0x0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")) - (105000, uint256("0x00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")) - (134444, uint256("0x00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")) - (168000, uint256("0x000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")) - (185333, uint256("0x00000000000002334c71b8706940c20348af897a9cfc0f1a6dab0d14d4ceb815")) - ; - - bool CheckBlock(int nHeight, const uint256& hash) + ( 0, hashGenesisBlockOfficial ) + ; // ppcoin: no checkpoint yet; to be created in future releases + + bool CheckHardened(int nHeight, const uint256& hash) { if (fTestNet) return true; // Testnet has no checkpoints @@ -50,7 +46,12 @@ namespace Checkpoints CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex) { - if (fTestNet) return NULL; + if (fTestNet) { + std::map::const_iterator t = mapBlockIndex.find(hashGenesisBlock); + if (t != mapBlockIndex.end()) + return t->second; + return NULL; + } BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, mapCheckpoints) { @@ -61,4 +62,293 @@ namespace Checkpoints } return NULL; } + + // ppcoin: synchronized checkpoint (centrally broadcasted) + uint256 hashSyncCheckpoint = 0; + uint256 hashPendingCheckpoint = 0; + CSyncCheckpoint checkpointMessage; + CSyncCheckpoint checkpointMessagePending; + uint256 hashInvalidCheckpoint = 0; + CCriticalSection cs_hashSyncCheckpoint; + + // ppcoin: get last synchronized checkpoint + CBlockIndex* GetLastSyncCheckpoint() + { + LOCK(cs_hashSyncCheckpoint); + if (!mapBlockIndex.count(hashSyncCheckpoint)) + error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str()); + else + return mapBlockIndex[hashSyncCheckpoint]; + return NULL; + } + + // ppcoin: only descendant of current sync-checkpoint is allowed + bool ValidateSyncCheckpoint(uint256 hashCheckpoint) + { + if (!mapBlockIndex.count(hashSyncCheckpoint)) + return error("ValidateSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str()); + if (!mapBlockIndex.count(hashCheckpoint)) + return error("ValidateSyncCheckpoint: block index missing for received sync-checkpoint %s", hashCheckpoint.ToString().c_str()); + + CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint]; + CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint]; + + if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight) + { + // Received an older checkpoint, trace back from current checkpoint + // to the same height of the received checkpoint to verify + // that current checkpoint should be a descendant block + CBlockIndex* pindex = pindexSyncCheckpoint; + while (pindex->nHeight > pindexCheckpointRecv->nHeight) + if (!(pindex = pindex->pprev)) + return error("ValidateSyncCheckpoint: pprev1 null - block index structure failure"); + if (pindex->GetBlockHash() != hashCheckpoint) + { + hashInvalidCheckpoint = hashCheckpoint; + return error("ValidateSyncCheckpoint: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str()); + } + return false; // ignore older checkpoint + } + + // Received checkpoint should be a descendant block of the current + // checkpoint. Trace back to the same height of current checkpoint + // to verify. + CBlockIndex* pindex = pindexCheckpointRecv; + while (pindex->nHeight > pindexSyncCheckpoint->nHeight) + if (!(pindex = pindex->pprev)) + return error("ValidateSyncCheckpoint: pprev2 null - block index structure failure"); + if (pindex->GetBlockHash() != hashSyncCheckpoint) + { + hashInvalidCheckpoint = hashCheckpoint; + return error("ValidateSyncCheckpoint: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str()); + } + return true; + } + + bool WriteSyncCheckpoint(const uint256& hashCheckpoint) + { + CTxDB txdb; + txdb.TxnBegin(); + if (!txdb.WriteSyncCheckpoint(hashCheckpoint)) + { + txdb.TxnAbort(); + return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str()); + } + if (!txdb.TxnCommit()) + return error("WriteSyncCheckpoint(): failed to commit to db sync checkpoint %s", hashCheckpoint.ToString().c_str()); + txdb.Close(); + + Checkpoints::hashSyncCheckpoint = hashCheckpoint; + return true; + } + + bool AcceptPendingSyncCheckpoint() + { + LOCK(cs_hashSyncCheckpoint); + if (hashPendingCheckpoint != 0 && mapBlockIndex.count(hashPendingCheckpoint)) + { + if (!ValidateSyncCheckpoint(hashPendingCheckpoint)) + { + hashPendingCheckpoint = 0; + checkpointMessagePending.SetNull(); + return false; + } + + CTxDB txdb; + CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint]; + if (!pindexCheckpoint->IsInMainChain()) + { + txdb.TxnBegin(); + if (!Reorganize(txdb, pindexCheckpoint)) + { + txdb.TxnAbort(); + hashInvalidCheckpoint = hashPendingCheckpoint; + return error("ProcessSyncCheckpoint: Reorganize failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str()); + } + } + txdb.Close(); + + if (!WriteSyncCheckpoint(hashPendingCheckpoint)) + return error("AcceptPendingSyncCheckpoint(): failed to write sync checkpoint %s", hashPendingCheckpoint.ToString().c_str()); + hashPendingCheckpoint = 0; + checkpointMessage = checkpointMessagePending; + checkpointMessagePending.SetNull(); + printf("AcceptPendingSyncCheckpoint : sync-checkpoint at %s\n", hashSyncCheckpoint.ToString().c_str()); + // relay the checkpoint + if (!checkpointMessage.IsNull()) + { + BOOST_FOREACH(CNode* pnode, vNodes) + checkpointMessage.RelayTo(pnode); + } + return true; + } + return false; + } + + uint256 AutoSelectSyncCheckpoint() + { + // select a block some time ago + CBlockIndex *pindex = mapBlockIndex[hashSyncCheckpoint]; + while (pindex->pnext && pindex->pnext->GetBlockTime() + CHECKPOINT_MIN_SPAN <= GetAdjustedTime()) + pindex = pindex->pnext; + return pindex->GetBlockHash(); + } + + // Check against synchronized checkpoint + bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev) + { + if (fTestNet) return true; // Testnet has no checkpoints + int nHeight = pindexPrev->nHeight + 1; + + LOCK(cs_hashSyncCheckpoint); + // sync-checkpoint should always be accepted block + assert(mapBlockIndex.count(hashSyncCheckpoint)); + const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint]; + + if (nHeight > pindexSync->nHeight) + { + // trace back to same height as sync-checkpoint + const CBlockIndex* pindex = pindexPrev; + while (pindex->nHeight > pindexSync->nHeight) + if (!(pindex = pindex->pprev)) + return error("CheckSync: pprev null - block index structure failure"); + if (pindex->nHeight < pindexSync->nHeight || pindex->GetBlockHash() != hashSyncCheckpoint) + return false; // only descendant of sync-checkpoint can pass check + } + if (nHeight == pindexSync->nHeight && hashBlock != hashSyncCheckpoint) + return false; // same height with sync-checkpoint + if (nHeight < pindexSync->nHeight && !mapBlockIndex.count(hashBlock)) + return false; // lower height than sync-checkpoint + return true; + } + + bool WantedByPendingSyncCheckpoint(uint256 hashBlock) + { + LOCK(cs_hashSyncCheckpoint); + if (hashPendingCheckpoint == 0) + return false; + if (hashBlock == hashPendingCheckpoint) + return true; + if (mapOrphanBlocks.count(hashPendingCheckpoint) + && hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint])) + return true; + return false; + } + + // ppcoin: reset synchronized checkpoint to last hardened checkpoint + bool ResetSyncCheckpoint() + { + LOCK(cs_hashSyncCheckpoint); + const uint256& hash = mapCheckpoints.rbegin()->second; + if (mapBlockIndex.count(hash) && !mapBlockIndex[hash]->IsInMainChain()) + { + // checkpoint block accepted but not yet in main chain + printf("ResetSyncCheckpoint: Reorganize to hardened checkpoint %s\n", hash.ToString().c_str()); + CTxDB txdb; + txdb.TxnBegin(); + if (!Reorganize(txdb, mapBlockIndex[hash])) + { + txdb.TxnAbort(); + return error("ResetSyncCheckpoint: Reorganize failed for hardened checkpoint %s", hash.ToString().c_str()); + } + txdb.Close(); + } + else if(!mapBlockIndex.count(hash)) + { + // checkpoint block not yet accepted + hashPendingCheckpoint = hash; + checkpointMessagePending.SetNull(); + printf("ResetSyncCheckpoint: pending for sync-checkpoint %s\n", hashPendingCheckpoint.ToString().c_str()); + } + + BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, mapCheckpoints) + { + const uint256& hash = i.second; + if (mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain()) + { + if (!WriteSyncCheckpoint(hash)) + return error("ResetSyncCheckpoint: failed to write sync checkpoint %s", hash.ToString().c_str()); + printf("ResetSyncCheckpoint: sync-checkpoint reset to %s\n", hashSyncCheckpoint.ToString().c_str()); + return true; + } + } + + return false; + } + + void AskForPendingSyncCheckpoint(CNode* pfrom) + { + LOCK(cs_hashSyncCheckpoint); + if (pfrom && hashPendingCheckpoint != 0 && (!mapBlockIndex.count(hashPendingCheckpoint)) && (!mapOrphanBlocks.count(hashPendingCheckpoint))) + pfrom->AskFor(CInv(MSG_BLOCK, hashPendingCheckpoint)); + } +} + +// ppcoin: sync-checkpoint master key +const std::string CSyncCheckpoint::strMasterPubKey = "0424f20205e5da98ba632bbd278a11a6499585f62bfb2c782377ef59f0251daab8085fc31471bcb8180bc75ed0fa41bb50c7c084511d54015a3a5241d645c7268a"; + +// ppcoin: verify signature of sync-checkpoint message +bool CSyncCheckpoint::CheckSignature() +{ + CKey key; + if (!key.SetPubKey(ParseHex(CSyncCheckpoint::strMasterPubKey))) + return error("CSyncCheckpoint::CheckSignature() : SetPubKey failed"); + if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) + return error("CSyncCheckpoint::CheckSignature() : verify signature failed"); + + // Now unserialize the data + CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); + sMsg >> *(CUnsignedSyncCheckpoint*)this; + return true; +} + +// ppcoin: process synchronized checkpoint +bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom) +{ + if (!CheckSignature()) + return false; + + LOCK(Checkpoints::cs_hashSyncCheckpoint); + if (!mapBlockIndex.count(hashCheckpoint)) + { + // We haven't received the checkpoint chain, keep the checkpoint as pending + Checkpoints::hashPendingCheckpoint = hashCheckpoint; + Checkpoints::checkpointMessagePending = *this; + printf("ProcessSyncCheckpoint: pending for sync-checkpoint %s\n", hashCheckpoint.ToString().c_str()); + // Ask this guy to fill in what we're missing + if (pfrom) + { + pfrom->PushGetBlocks(pindexBest, hashCheckpoint); + // ask directly as well in case rejected earlier by duplicate + // proof-of-stake because getblocks may not get it this time + pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint]) : hashCheckpoint)); + } + return false; + } + + if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint)) + return false; + + CTxDB txdb; + CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint]; + if (!pindexCheckpoint->IsInMainChain()) + { + // checkpoint chain received but not yet main chain + txdb.TxnBegin(); + if (!Reorganize(txdb, pindexCheckpoint)) + { + txdb.TxnAbort(); + Checkpoints::hashInvalidCheckpoint = hashCheckpoint; + return error("ProcessSyncCheckpoint: Reorganize failed for sync checkpoint %s", hashCheckpoint.ToString().c_str()); + } + } + txdb.Close(); + + if (!Checkpoints::WriteSyncCheckpoint(hashCheckpoint)) + return error("ProcessSyncCheckpoint(): failed to write sync checkpoint %s", hashCheckpoint.ToString().c_str()); + Checkpoints::checkpointMessage = *this; + Checkpoints::hashPendingCheckpoint = 0; + Checkpoints::checkpointMessagePending.SetNull(); + printf("ProcessSyncCheckpoint: sync-checkpoint at %s\n", hashCheckpoint.ToString().c_str()); + return true; } diff --git a/src/checkpoints.h b/src/checkpoints.h index 70e9365..ff815c8 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -1,13 +1,20 @@ // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_CHECKPOINT_H #define BITCOIN_CHECKPOINT_H #include +#include "net.h" +#include "util.h" + +#define STAKE_MIN_AGE (60 * 60 * 24) // minimum age for coin age +#define CHECKPOINT_MIN_SPAN (60 * 60 * 4) // 4 hours checkpoint class uint256; class CBlockIndex; +class CSyncCheckpoint; /** Block-chain checkpoints are compiled-in sanity checks. * They are updated every release or three. @@ -15,13 +22,116 @@ class CBlockIndex; namespace Checkpoints { // Returns true if block passes checkpoint checks - bool CheckBlock(int nHeight, const uint256& hash); + bool CheckHardened(int nHeight, const uint256& hash); // Return conservative estimate of total number of blocks, 0 if unknown int GetTotalBlocksEstimate(); // Returns last CBlockIndex* in mapBlockIndex that is a checkpoint CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex); + + extern uint256 hashSyncCheckpoint; + extern CSyncCheckpoint checkpointMessage; + extern uint256 hashInvalidCheckpoint; + extern CCriticalSection cs_hashSyncCheckpoint; + + CBlockIndex* GetLastSyncCheckpoint(); + bool WriteSyncCheckpoint(const uint256& hashCheckpoint); + bool AcceptPendingSyncCheckpoint(); + uint256 AutoSelectSyncCheckpoint(); + bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev); + bool WantedByPendingSyncCheckpoint(uint256 hashBlock); + bool ResetSyncCheckpoint(); + void AskForPendingSyncCheckpoint(CNode* pfrom); } +// ppcoin: synchronized checkpoint +class CUnsignedSyncCheckpoint +{ +public: + int nVersion; + uint256 hashCheckpoint; // checkpoint block + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(hashCheckpoint); + ) + + void SetNull() + { + nVersion = 1; + hashCheckpoint = 0; + } + + std::string ToString() const + { + return strprintf( + "CSyncCheckpoint(\n" + " nVersion = %d\n" + " hashCheckpoint = %s\n" + ")\n", + nVersion, + hashCheckpoint.ToString().c_str()); + } + + void print() const + { + printf("%s", ToString().c_str()); + } +}; + +class CSyncCheckpoint : public CUnsignedSyncCheckpoint +{ +public: + static const std::string strMasterPubKey; + + std::vector vchMsg; + std::vector vchSig; + + CSyncCheckpoint() + { + SetNull(); + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(vchMsg); + READWRITE(vchSig); + ) + + void SetNull() + { + CUnsignedSyncCheckpoint::SetNull(); + vchMsg.clear(); + vchSig.clear(); + } + + bool IsNull() const + { + return (hashCheckpoint == 0); + } + + uint256 GetHash() const + { + return SerializeHash(*this); + } + + bool RelayTo(CNode* pnode) const + { + // returns true if wasn't already sent + if (pnode->hashCheckpointKnown != hashCheckpoint) + { + pnode->hashCheckpointKnown = hashCheckpoint; + pnode->PushMessage("checkpoint", *this); + return true; + } + return false; + } + + bool CheckSignature(); + bool ProcessSyncCheckpoint(CNode* pfrom); +}; + #endif diff --git a/src/db.cpp b/src/db.cpp index c67a34c..04f400e 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -1,9 +1,12 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "db.h" +#include "net.h" +#include "checkpoints.h" #include "util.h" #include "main.h" #include @@ -485,14 +488,34 @@ bool CTxDB::WriteHashBestChain(uint256 hashBestChain) return Write(string("hashBestChain"), hashBestChain); } -bool CTxDB::ReadBestInvalidWork(CBigNum& bnBestInvalidWork) +bool CTxDB::ReadBestInvalidTrust(uint64& nBestInvalidTrust) { - return Read(string("bnBestInvalidWork"), bnBestInvalidWork); + return Read(string("nBestInvalidTrust"), nBestInvalidTrust); } -bool CTxDB::WriteBestInvalidWork(CBigNum bnBestInvalidWork) +bool CTxDB::WriteBestInvalidTrust(uint64 nBestInvalidTrust) { - return Write(string("bnBestInvalidWork"), bnBestInvalidWork); + return Write(string("nBestInvalidTrust"), nBestInvalidTrust); +} + +bool CTxDB::ReadSyncCheckpoint(uint256& hashCheckpoint) +{ + return Read(string("hashSyncCheckpoint"), hashCheckpoint); +} + +bool CTxDB::WriteSyncCheckpoint(uint256 hashCheckpoint) +{ + return Write(string("hashSyncCheckpoint"), hashCheckpoint); +} + +bool CTxDB::ReadCheckpointPubKey(string& strPubKey) +{ + return Read(string("strCheckpointPubKey"), strPubKey); +} + +bool CTxDB::WriteCheckpointPubKey(const string& strPubKey) +{ + return Write(string("strCheckpointPubKey"), strPubKey); } CBlockIndex static * InsertBlockIndex(uint256 hash) @@ -554,7 +577,10 @@ bool CTxDB::LoadBlockIndex() pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); pindexNew->nFile = diskindex.nFile; pindexNew->nBlockPos = diskindex.nBlockPos; + pindexNew->nChainTrust = diskindex.nChainTrust; pindexNew->nHeight = diskindex.nHeight; + pindexNew->fProofOfStake = diskindex.fProofOfStake; + pindexNew->prevoutStake = diskindex.prevoutStake; pindexNew->nVersion = diskindex.nVersion; pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; pindexNew->nTime = diskindex.nTime; @@ -567,6 +593,10 @@ bool CTxDB::LoadBlockIndex() if (!pindexNew->CheckIndex()) return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight); + + // ppcoin: build setStakeSeen + if (pindexNew->fProofOfStake) + setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); } else { @@ -582,21 +612,6 @@ bool CTxDB::LoadBlockIndex() if (fRequestShutdown) return true; - // Calculate bnChainWork - vector > vSortedByHeight; - vSortedByHeight.reserve(mapBlockIndex.size()); - BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex) - { - CBlockIndex* pindex = item.second; - vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex)); - } - sort(vSortedByHeight.begin(), vSortedByHeight.end()); - BOOST_FOREACH(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight) - { - CBlockIndex* pindex = item.second; - pindex->bnChainWork = (pindex->pprev ? pindex->pprev->bnChainWork : 0) + pindex->GetBlockWork(); - } - // Load hashBestChain pointer to end of best chain if (!ReadHashBestChain(hashBestChain)) { @@ -608,11 +623,16 @@ bool CTxDB::LoadBlockIndex() return error("CTxDB::LoadBlockIndex() : hashBestChain not found in the block index"); pindexBest = mapBlockIndex[hashBestChain]; nBestHeight = pindexBest->nHeight; - bnBestChainWork = pindexBest->bnChainWork; - printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight); + nBestChainTrust = pindexBest->nChainTrust; + printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%d\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, nBestChainTrust); + + // ppcoin: load hashSyncCheckpoint + if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint)) + return error("CTxDB::LoadBlockIndex() : hashSyncCheckpoint not loaded"); + printf("LoadBlockIndex(): synchronized checkpoint %s\n", Checkpoints::hashSyncCheckpoint.ToString().c_str()); - // Load bnBestInvalidWork, OK if it doesn't exist - ReadBestInvalidWork(bnBestInvalidWork); + // Load nBestInvalidTrust, OK if it doesn't exist + ReadBestInvalidTrust(nBestInvalidTrust); // Verify blocks in the best chain int nCheckLevel = GetArg("-checklevel", 1); diff --git a/src/db.h b/src/db.h index acb531f..792d5ca 100644 --- a/src/db.h +++ b/src/db.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_DB_H @@ -293,8 +294,12 @@ public: bool EraseBlockIndex(uint256 hash); bool ReadHashBestChain(uint256& hashBestChain); bool WriteHashBestChain(uint256 hashBestChain); - bool ReadBestInvalidWork(CBigNum& bnBestInvalidWork); - bool WriteBestInvalidWork(CBigNum bnBestInvalidWork); + bool ReadBestInvalidTrust(uint64& nBestInvalidTrust); + bool WriteBestInvalidTrust(uint64 nBestInvalidTrust); + bool ReadSyncCheckpoint(uint256& hashCheckpoint); + bool WriteSyncCheckpoint(uint256 hashCheckpoint); + bool ReadCheckpointPubKey(std::string& strPubKey); + bool WriteCheckpointPubKey(const std::string& strPubKey); bool LoadBlockIndex(); }; diff --git a/src/init.cpp b/src/init.cpp index 281a8ca..65386e8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "db.h" @@ -73,7 +74,7 @@ void Shutdown(void* parg) delete pwalletMain; CreateThread(ExitTimeout, NULL); Sleep(50); - printf("Bitcoin exiting\n\n"); + printf("PPCoin exiting\n\n"); fExit = true; #ifndef QT_GUI // ensure non UI client get's exited here, but let Bitcoin-Qt reach return 0; in bitcoin.cpp @@ -104,6 +105,7 @@ void HandleSIGTERM(int) // Start // #if !defined(QT_GUI) +#if !defined(PPCOIN_GENESIS) int main(int argc, char* argv[]) { bool fRet = false; @@ -115,6 +117,7 @@ int main(int argc, char* argv[]) return 1; } #endif +#endif bool AppInit(int argc, char* argv[]) { @@ -175,15 +178,15 @@ bool AppInit2(int argc, char* argv[]) if (mapArgs.count("-?") || mapArgs.count("--help")) { string strUsage = string() + - _("Bitcoin version") + " " + FormatFullVersion() + "\n\n" + + _("PPCoin version") + " " + FormatFullVersion() + "\n\n" + _("Usage:") + "\t\t\t\t\t\t\t\t\t\t\n" + - " bitcoind [options] \t " + "\n" + - " bitcoind [options] [params]\t " + _("Send command to -server or bitcoind") + "\n" + - " bitcoind [options] help \t\t " + _("List commands") + "\n" + - " bitcoind [options] help \t\t " + _("Get help for a command") + "\n" + + " ppcoind [options] \t " + "\n" + + " ppcoind [options] [params]\t " + _("Send command to -server or ppcoind") + "\n" + + " ppcoind [options] help \t\t " + _("List commands") + "\n" + + " ppcoind [options] help \t\t " + _("Get help for a command") + "\n" + _("Options:") + "\n" + - " -conf= \t\t " + _("Specify configuration file (default: bitcoin.conf)") + "\n" + - " -pid= \t\t " + _("Specify pid file (default: bitcoind.pid)") + "\n" + + " -conf= \t\t " + _("Specify configuration file (default: ppcoin.conf)") + "\n" + + " -pid= \t\t " + _("Specify pid file (default: ppcoind.pid)") + "\n" + " -gen \t\t " + _("Generate coins") + "\n" + " -gen=0 \t\t " + _("Don't generate coins") + "\n" + " -min \t\t " + _("Start minimized") + "\n" + @@ -194,11 +197,10 @@ bool AppInit2(int argc, char* argv[]) " -timeout= \t " + _("Specify connection timeout (in milliseconds)") + "\n" + " -proxy= \t " + _("Connect through socks4 proxy") + "\n" + " -dns \t " + _("Allow DNS lookups for addnode and connect") + "\n" + - " -port= \t\t " + _("Listen for connections on (default: 8333 or testnet: 18333)") + "\n" + + " -port= \t\t " + _("Listen for connections on (default: 9901 or testnet: 9903)") + "\n" + " -maxconnections=\t " + _("Maintain at most connections to peers (default: 125)") + "\n" + " -addnode= \t " + _("Add a node to connect to and attempt to keep the connection open") + "\n" + " -connect= \t\t " + _("Connect only to the specified node") + "\n" + - " -irc \t " + _("Find peers using internet relay chat (default: 0)") + "\n" + " -listen \t " + _("Accept connections from outside (default: 1)") + "\n" + #ifdef QT_GUI " -lang= \t\t " + _("Set language, for example \"de_DE\" (default: system locale)") + "\n" + @@ -232,7 +234,7 @@ bool AppInit2(int argc, char* argv[]) #endif " -rpcuser= \t " + _("Username for JSON-RPC connections") + "\n" + " -rpcpassword=\t " + _("Password for JSON-RPC connections") + "\n" + - " -rpcport= \t\t " + _("Listen for JSON-RPC connections on (default: 8332)") + "\n" + + " -rpcport= \t\t " + _("Listen for JSON-RPC connections on (default: 9902)") + "\n" + " -rpcallowip= \t\t " + _("Allow JSON-RPC connections from specified IP address") + "\n" + " -rpcconnect= \t " + _("Send commands to node running on (default: 127.0.0.1)") + "\n" + " -blocknotify= " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" + @@ -328,7 +330,7 @@ bool AppInit2(int argc, char* argv[]) if (!fDebug) ShrinkDebugFile(); printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - printf("Bitcoin version %s (%s)\n", FormatFullVersion().c_str(), CLIENT_DATE.c_str()); + printf("PPCoin version %s (%s)\n", FormatFullVersion().c_str(), CLIENT_DATE.c_str()); printf("Default data directory %s\n", GetDefaultDataDir().string().c_str()); if (GetBoolArg("-loadblockindextest")) @@ -346,7 +348,7 @@ bool AppInit2(int argc, char* argv[]) static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); if (!lock.try_lock()) { - ThreadSafeMessageBox(strprintf(_("Cannot obtain a lock on data directory %s. Bitcoin is probably already running."), GetDataDir().string().c_str()), _("Bitcoin"), wxOK|wxMODAL); + ThreadSafeMessageBox(strprintf(_("Cannot obtain a lock on data directory %s. PPCoin is probably already running."), GetDataDir().string().c_str()), _("PPCoin"), wxOK|wxMODAL); return false; } @@ -355,7 +357,7 @@ bool AppInit2(int argc, char* argv[]) // Load data files // if (fDaemon) - fprintf(stdout, "bitcoin server starting\n"); + fprintf(stdout, "ppcoin server starting\n"); int64 nStart; InitMessage(_("Loading addresses...")); @@ -392,12 +394,12 @@ bool AppInit2(int argc, char* argv[]) if (nLoadWalletRet == DB_CORRUPT) strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n"; else if (nLoadWalletRet == DB_TOO_NEW) - strErrors << _("Error loading wallet.dat: Wallet requires newer version of Bitcoin") << "\n"; + strErrors << _("Error loading wallet.dat: Wallet requires newer version of PPCoin") << "\n"; else if (nLoadWalletRet == DB_NEED_REWRITE) { - strErrors << _("Wallet needed to be rewritten: restart Bitcoin to complete") << "\n"; + strErrors << _("Wallet needed to be rewritten: restart PPCoin to complete") << "\n"; printf("%s", strErrors.str().c_str()); - ThreadSafeMessageBox(strErrors.str(), _("Bitcoin"), wxOK | wxICON_ERROR | wxMODAL); + ThreadSafeMessageBox(strErrors.str(), _("PPCoin"), wxOK | wxICON_ERROR | wxMODAL); return false; } else @@ -469,7 +471,7 @@ bool AppInit2(int argc, char* argv[]) if (!strErrors.str().empty()) { - ThreadSafeMessageBox(strErrors.str(), _("Bitcoin"), wxOK | wxICON_ERROR | wxMODAL); + ThreadSafeMessageBox(strErrors.str(), _("PPCoin"), wxOK | wxICON_ERROR | wxMODAL); return false; } @@ -525,7 +527,7 @@ bool AppInit2(int argc, char* argv[]) addrProxy = CService(mapArgs["-proxy"], 9050); if (!addrProxy.IsValid()) { - ThreadSafeMessageBox(_("Invalid -proxy address"), _("Bitcoin"), wxOK | wxMODAL); + ThreadSafeMessageBox(_("Invalid -proxy address"), _("PPCcoin"), wxOK | wxMODAL); return false; } } @@ -556,7 +558,7 @@ bool AppInit2(int argc, char* argv[]) std::string strError; if (!BindListenPort(strError)) { - ThreadSafeMessageBox(strError, _("Bitcoin"), wxOK | wxMODAL); + ThreadSafeMessageBox(strError, _("PPCoin"), wxOK | wxMODAL); return false; } } @@ -574,13 +576,13 @@ bool AppInit2(int argc, char* argv[]) if (mapArgs.count("-paytxfee")) { - if (!ParseMoney(mapArgs["-paytxfee"], nTransactionFee)) + if (!ParseMoney(mapArgs["-paytxfee"], nTransactionFee) || nTransactionFee < MIN_TX_FEE) { - ThreadSafeMessageBox(_("Invalid amount for -paytxfee="), _("Bitcoin"), wxOK | wxMODAL); + ThreadSafeMessageBox(_("Invalid amount for -paytxfee="), _("PPCoin"), wxOK | wxMODAL); return false; } if (nTransactionFee > 0.25 * COIN) - ThreadSafeMessageBox(_("Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction."), _("Bitcoin"), wxOK | wxICON_EXCLAMATION | wxMODAL); + ThreadSafeMessageBox(_("Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction."), _("PPCoin"), wxOK | wxICON_EXCLAMATION | wxMODAL); } // @@ -592,7 +594,7 @@ bool AppInit2(int argc, char* argv[]) RandAddSeedPerfmon(); if (!CreateThread(StartNode, NULL)) - ThreadSafeMessageBox(_("Error: CreateThread(StartNode) failed"), _("Bitcoin"), wxOK | wxMODAL); + ThreadSafeMessageBox(_("Error: CreateThread(StartNode) failed"), _("PPCoin"), wxOK | wxMODAL); if (fServer) CreateThread(ThreadRPCServer, NULL); diff --git a/src/main.cpp b/src/main.cpp index 9a7ff16..25634df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -28,12 +29,13 @@ CTxMemPool mempool; unsigned int nTransactionsUpdated = 0; map mapBlockIndex; -uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); +set > setStakeSeen; +uint256 hashGenesisBlock = hashGenesisBlockOfficial; static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32); CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; -CBigNum bnBestChainWork = 0; -CBigNum bnBestInvalidWork = 0; +uint64 nBestChainTrust = 0; +uint64 nBestInvalidTrust = 0; uint256 hashBestChain = 0; CBlockIndex* pindexBest = NULL; int64 nTimeBestReceived = 0; @@ -42,6 +44,7 @@ CMedianFilter cPeerBlockCounts(5, 0); // Amount of blocks that other nodes map mapOrphanBlocks; multimap mapOrphanBlocksByPrev; +set > setStakeSeenOrphan; map mapOrphanTransactions; map > mapOrphanTransactionsByPrev; @@ -56,6 +59,7 @@ int64 nHPSTimerStart; // Settings int64 nTransactionFee = 0; +int64 nBalanceReserve = 0; @@ -109,8 +113,20 @@ void static EraseFromWallets(uint256 hash) } // make sure all wallets know about the given transaction, in the given block -void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false) +void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true) { + if (!fConnect) + { + // ppcoin: wallets need to refund inputs when disconnecting coinstake + if (tx.IsCoinStake()) + { + BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) + if (pwallet->IsFromMe(tx)) + pwallet->DisableTransaction(tx); + } + return; + } + BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered) pwallet->AddToWalletIfInvolvingMe(tx, pblock, fUpdate); } @@ -435,8 +451,11 @@ bool CTransaction::CheckTransaction() const // Check for negative or overflow output values int64 nValueOut = 0; - BOOST_FOREACH(const CTxOut& txout, vout) + for (int i = 0; i < vout.size(); i++) { + const CTxOut& txout = vout[i]; + if (txout.IsEmpty() && (!IsCoinBase()) && (!IsCoinStake())) + return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction")); if (txout.nValue < 0) return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue negative")); if (txout.nValue > MAX_MONEY) @@ -482,6 +501,9 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) return tx.DoS(100, error("CTxMemPool::accept() : coinbase as individual tx")); + // ppcoin: coinstake is also only valid in a block, not as a loose transaction + if (tx.IsCoinStake()) + return tx.DoS(100, error("CTxMemPool::accept() : coinstake as individual tx")); // To help v0.1.5 clients who would see it as a negative number if ((int64)tx.nLockTime > std::numeric_limits::max()) @@ -556,7 +578,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block - if (nFees < tx.GetMinFee(1000, true, GMF_RELAY)) + if (nFees < tx.GetMinFee(1000, false, GMF_RELAY)) return error("CTxMemPool::accept() : not enough fees"); // Continuously rate-limit free transactions @@ -586,7 +608,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!tx.ConnectInputs(mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false)) + if (!tx.ConnectInputs(txdb, mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false)) { return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); } @@ -684,7 +706,7 @@ int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const int CMerkleTx::GetBlocksToMaturity() const { - if (!IsCoinBase()) + if (!(IsCoinBase() || IsCoinStake())) return 0; return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); } @@ -720,7 +742,7 @@ bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) // Add previous supporting transactions first BOOST_FOREACH(CMerkleTx& tx, vtxPrev) { - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) { uint256 hash = tx.GetHash(); if (!mempool.exists(hash) && !txdb.ContainsTx(hash)) @@ -790,19 +812,61 @@ uint256 static GetOrphanRoot(const CBlock* pblock) return pblock->GetHash(); } -int64 static GetBlockValue(int nHeight, int64 nFees) +// ppcoin: find block wanted by given orphan block +uint256 WantedByOrphan(const CBlock* pblockOrphan) +{ + // Work back to the first block in the orphan chain + while (mapOrphanBlocks.count(pblockOrphan->hashPrevBlock)) + pblockOrphan = mapOrphanBlocks[pblockOrphan->hashPrevBlock]; + return pblockOrphan->hashPrevBlock; +} + +int64 static GetProofOfWorkReward(unsigned int nBits) { - int64 nSubsidy = 50 * COIN; + CBigNum bnSubsidyLimit = 9999 * COIN; // subsidy amount for difficulty 1 + CBigNum bnTarget; + bnTarget.SetCompact(nBits); + CBigNum bnTargetLimit = bnProofOfWorkLimit; + bnTargetLimit.SetCompact(bnTargetLimit.GetCompact()); + + // ppcoin: subsidy is cut in half every 16x multiply of difficulty + // A reasonably continuous curve is used to avoid shock to market + // (nSubsidyLimit / nSubsidy) ** 4 == bnProofOfWorkLimit / bnTarget + CBigNum bnLowerBound = CENT; + CBigNum bnUpperBound = bnSubsidyLimit; + while (bnLowerBound + CENT <= bnUpperBound) + { + CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2; + if (fDebug && GetBoolArg("-printcreation")) + printf("GetProofOfWorkReward() : lower=%"PRI64d" upper=%"PRI64d" mid=%"PRI64d"\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64()); + if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget) + bnUpperBound = bnMidValue; + else + bnLowerBound = bnMidValue; + } + + int64 nSubsidy = bnUpperBound.getuint64(); + nSubsidy = (nSubsidy / CENT) * CENT; + if (fDebug && GetBoolArg("-printcreation")) + printf("GetProofOfWorkReward() : create=%s nBits=0x%08x nSubsidy=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nBits, nSubsidy); - // Subsidy is cut in half every 4 years - nSubsidy >>= (nHeight / 210000); + return nSubsidy; +} - return nSubsidy + nFees; +// ppcoin: miner's coin stake is rewarded based on coin age spent (coin-days) +int64 GetProofOfStakeReward(int64 nCoinAge) +{ + static int64 nRewardCoinYear = CENT; // creation amount per coin-year + int64 nSubsidy = nCoinAge * 33 / (365 * 33 + 8) * nRewardCoinYear; + if (fDebug && GetBoolArg("-printcreation")) + printf("GetProofOfStakeReward(): create=%s nCoinAge=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nCoinAge); + return nSubsidy; } -static const int64 nTargetTimespan = 14 * 24 * 60 * 60; // two weeks -static const int64 nTargetSpacing = 10 * 60; -static const int64 nInterval = nTargetTimespan / nTargetSpacing; +static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week +static const int64 nTargetSpacingStake = 10 * 60; // ten minutes +static const int64 nTargetSpacingWorkMax = 2 * 60 * 60; // two hours +static const int64 nMaxClockDrift = 2 * 60 * 60; // two hours // // minimum amount of work that could possibly be required nTime after @@ -810,85 +874,54 @@ static const int64 nInterval = nTargetTimespan / nTargetSpacing; // unsigned int ComputeMinWork(unsigned int nBase, int64 nTime) { - // Testnet has min-difficulty blocks - // after nTargetSpacing*2 time between blocks: - if (fTestNet && nTime > nTargetSpacing*2) - return bnProofOfWorkLimit.GetCompact(); - CBigNum bnResult; bnResult.SetCompact(nBase); + bnResult *= 2; while (nTime > 0 && bnResult < bnProofOfWorkLimit) { - // Maximum 400% adjustment... - bnResult *= 4; - // ... in best-case exactly 4-times-normal target time - nTime -= nTargetTimespan*4; + // Maximum 200% adjustment per day... + bnResult *= 2; + nTime -= 24 * 60 * 60; } if (bnResult > bnProofOfWorkLimit) bnResult = bnProofOfWorkLimit; return bnResult.GetCompact(); } -unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlock *pblock) +// ppcoin: find last block index up to pindex +const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) { - unsigned int nProofOfWorkLimit = bnProofOfWorkLimit.GetCompact(); - - // Genesis block - if (pindexLast == NULL) - return nProofOfWorkLimit; - - // Only change once per interval - if ((pindexLast->nHeight+1) % nInterval != 0) - { - // Special rules for testnet after 15 Feb 2012: - if (fTestNet && pblock->nTime > 1329264000) - { - // If the new block's timestamp is more than 2* 10 minutes - // then allow mining of a min-difficulty block. - if (pblock->nTime - pindexLast->nTime > nTargetSpacing*2) - return nProofOfWorkLimit; - else - { - // Return the last non-special-min-difficulty-rules-block - const CBlockIndex* pindex = pindexLast; - while (pindex->pprev && pindex->nHeight % nInterval != 0 && pindex->nBits == nProofOfWorkLimit) - pindex = pindex->pprev; - return pindex->nBits; - } - } - - return pindexLast->nBits; - } + while (pindex && (pindex->IsProofOfStake() != fProofOfStake)) + pindex = pindex->pprev; + return pindex; +} - // Go back by what we want to be 14 days worth of blocks - const CBlockIndex* pindexFirst = pindexLast; - for (int i = 0; pindexFirst && i < nInterval-1; i++) - pindexFirst = pindexFirst->pprev; - assert(pindexFirst); +unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) +{ + // Genesis block and first block + if (pindexLast == NULL || pindexLast->pprev == NULL) + return bnProofOfWorkLimit.GetCompact(); - // Limit adjustment step - int64 nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime(); - printf(" nActualTimespan = %"PRI64d" before bounds\n", nActualTimespan); - if (nActualTimespan < nTargetTimespan/4) - nActualTimespan = nTargetTimespan/4; - if (nActualTimespan > nTargetTimespan*4) - nActualTimespan = nTargetTimespan*4; + const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake); + if (pindexPrev == NULL) + return bnProofOfWorkLimit.GetCompact(); + const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake); + if (pindexPrevPrev == NULL) + return bnProofOfWorkLimit.GetCompact(); + int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime(); - // Retarget + // ppcoin: target change every block + // ppcoin: retarget with exponential moving toward target spacing CBigNum bnNew; - bnNew.SetCompact(pindexLast->nBits); - bnNew *= nActualTimespan; - bnNew /= nTargetTimespan; + bnNew.SetCompact(pindexPrev->nBits); + int64 nTargetSpacing = fProofOfStake? nTargetSpacingStake : min(nTargetSpacingWorkMax, nTargetSpacingStake * (1 + pindexLast->nHeight - pindexPrev->nHeight)); + int64 nInterval = nTargetTimespan / nTargetSpacing; + bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing); + bnNew /= ((nInterval + 1) * nTargetSpacing); if (bnNew > bnProofOfWorkLimit) bnNew = bnProofOfWorkLimit; - /// debug print - printf("GetNextWorkRequired RETARGET\n"); - printf("nTargetTimespan = %"PRI64d" nActualTimespan = %"PRI64d"\n", nTargetTimespan, nActualTimespan); - printf("Before: %08x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str()); - printf("After: %08x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); - return bnNew.GetCompact(); } @@ -931,25 +964,21 @@ bool IsInitialBlockDownload() void static InvalidChainFound(CBlockIndex* pindexNew) { - if (pindexNew->bnChainWork > bnBestInvalidWork) + if (pindexNew->nChainTrust > nBestInvalidTrust) { - bnBestInvalidWork = pindexNew->bnChainWork; - CTxDB().WriteBestInvalidWork(bnBestInvalidWork); + nBestInvalidTrust = pindexNew->nChainTrust; + CTxDB().WriteBestInvalidTrust(nBestInvalidTrust); MainFrameRepaint(); } - printf("InvalidChainFound: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str()); - printf("InvalidChainFound: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); - if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6) + printf("InvalidChainFound: invalid block=%s height=%d trust=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, CBigNum(pindexNew->nChainTrust).ToString().c_str()); + printf("InvalidChainFound: current best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str()); + if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6) printf("InvalidChainFound: WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade.\n"); } void CBlock::UpdateTime(const CBlockIndex* pindexPrev) { - nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - - // Updating time can change work required on testnet: - if (fTestNet) - nBits = GetNextWorkRequired(pindexPrev, this); + nTime = max(GetBlockTime(), GetAdjustedTime()); } @@ -1115,7 +1144,7 @@ unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const return nSigOps; } -bool CTransaction::ConnectInputs(MapPrevTx inputs, +bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map& mapTestPool, const CDiskTxPos& posThisTx, const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash) { @@ -1137,11 +1166,15 @@ bool CTransaction::ConnectInputs(MapPrevTx inputs, if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); - // If prev is coinbase, check that it's matured - if (txPrev.IsCoinBase()) + // If prev is coinbase/coinstake, check that it's matured + if (txPrev.IsCoinBase() || txPrev.IsCoinStake()) for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) - return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); + return error("ConnectInputs() : tried to spend coinbase/coinstake at depth %d", pindexBlock->nHeight - pindex->nHeight); + + // ppcoin: check transaction timestamp + if (txPrev.nTime > nTime) + return DoS(100, error("ConnectInputs() : transaction timestamp earlier than input transaction")); // Check for negative or overflow input values nValueIn += txPrev.vout[prevout.n].nValue; @@ -1192,16 +1225,32 @@ bool CTransaction::ConnectInputs(MapPrevTx inputs, } } - if (nValueIn < GetValueOut()) - return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); + if (IsCoinStake()) + { + // ppcoin: coin stake tx earns reward instead of paying fee + uint64 nCoinAge; + if (!GetCoinAge(txdb, nCoinAge)) + return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str()); + int64 nStakeReward = GetValueOut() - nValueIn; + if (nStakeReward > GetProofOfStakeReward(nCoinAge)) + return DoS(100, error("ConnectInputs() : %s stake reward exceeded", GetHash().ToString().substr(0,10).c_str())); + } + else + { + if (nValueIn < GetValueOut()) + return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); - // Tally transaction fees - int64 nTxFee = nValueIn - GetValueOut(); - if (nTxFee < 0) - return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); - nFees += nTxFee; - if (!MoneyRange(nFees)) - return DoS(100, error("ConnectInputs() : nFees out of range")); + // Tally transaction fees + int64 nTxFee = nValueIn - GetValueOut(); + if (nTxFee < 0) + return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); + // ppcoin: enforce transaction fees for every block + if (nTxFee < GetMinFee()) + return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false; + nFees += nTxFee; + if (!MoneyRange(nFees)) + return DoS(100, error("ConnectInputs() : nFees out of range")); + } } return true; @@ -1274,6 +1323,10 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("DisconnectBlock() : WriteBlockIndex failed"); } + // ppcoin: clean up wallet after disconnecting coinstake + BOOST_FOREACH(CTransaction& tx, vtx) + SyncWithWallets(tx, this, false, false); + return true; } @@ -1312,7 +1365,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) bool fStrictPayToScriptHash = (pindex->nTime >= nBIP16SwitchTime); //// issue here: it doesn't know the version - unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - 1 + GetSizeOfCompactSize(vtx.size()); + unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); map mapQueuedChanges; int64 nFees = 0; @@ -1327,7 +1380,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); MapPrevTx mapInputs; - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) { bool fInvalid; if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid)) @@ -1345,7 +1398,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut(); - if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash)) + if (!tx.ConnectInputs(txdb, mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash)) return false; } @@ -1359,8 +1412,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("ConnectBlock() : UpdateTxIndex failed"); } - if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) + // ppcoin: fees are not collected by miners as in bitcoin + // ppcoin: fees are destroyed to compensate the entire network + if (IsProofOfWork() && vtx[0].GetValueOut() > GetProofOfWorkReward(nBits)) return false; + if (fDebug && GetBoolArg("-printcreation")) + printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees); // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. @@ -1379,7 +1436,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { printf("REORGANIZE\n"); @@ -1423,7 +1480,7 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Queue memory transactions to resurrect BOOST_FOREACH(const CTransaction& tx, block.vtx) - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) vResurrect.push_back(tx); } @@ -1539,7 +1596,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // Reorganize is costly in terms of db load, as it works in a single db transaction. // Try to limit how much needs to be done inside - while (pindexIntermediate->pprev && pindexIntermediate->pprev->bnChainWork > pindexBest->bnChainWork) + while (pindexIntermediate->pprev && pindexIntermediate->pprev->nChainTrust > pindexBest->nChainTrust) { vpindexSecondary.push_back(pindexIntermediate); pindexIntermediate = pindexIntermediate->pprev; @@ -1587,10 +1644,10 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) hashBestChain = hash; pindexBest = pindexNew; nBestHeight = pindexBest->nHeight; - bnBestChainWork = pindexNew->bnChainWork; + nBestChainTrust = pindexNew->nChainTrust; nTimeBestReceived = GetTime(); nTransactionsUpdated++; - printf("SetBestChain: new best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); + printf("SetBestChain: new best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str()); std::string strCmd = GetArg("-blocknotify", ""); @@ -1604,6 +1661,138 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) } +// ppcoin: coinstake must meet hash target according to the protocol: +// input 0 must meet the formula +// hash(nBits + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDay +// this ensures that the chance of getting a coinstake is proportional to the +// amount of coin age one owns. +// The reason this hash is chosen is the following: +// nBits: encodes all past block timestamps, making computing hash in advance +// more difficult +// txPrev.block.nTime: prevent nodes from guessing a good timestamp to +// generate transaction for future advantage +// txPrev.offset: offset of txPrev inside block, to reduce the chance of +// nodes generating coinstake at the same time +// txPrev.nTime: reduce the chance of nodes generating coinstake at the same +// time +// txPrev.vout.n: output number of txPrev, to reduce the chance of nodes +// generating coinstake at the same time +// block/tx hash should not be used here as they can be generated in vast +// quantities so as to generate blocks faster, degrading the system back into +// a proof-of-work situation. +// +bool CTransaction::CheckProofOfStake(unsigned int nBits) const +{ + CBigNum bnTargetPerCoinDay; + bnTargetPerCoinDay.SetCompact(nBits); + + if (!IsCoinStake()) + return true; + + // Input 0 must match the stake hash target per coin age (nBits) + const CTxIn& txin = vin[0]; + + // First try finding the previous transaction in database + CTxDB txdb("r"); + CTransaction txPrev; + CTxIndex txindex; + if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) + return false; // previous transaction not in main chain + txdb.Close(); + if (nTime < txPrev.nTime) + return false; // Transaction timestamp violation + + // Verify signature + if (!VerifySignature(txPrev, *this, 0, true, 0)) + return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str())); + + // Read block header + CBlock block; + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + return false; // unable to read block of previous transaction + if (block.GetBlockTime() + STAKE_MIN_AGE > nTime) + return false; // only count coins meeting min age requirement + + int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + CBigNum bnCoinDay = CBigNum(nValueIn) * (nTime-txPrev.nTime) / COIN / (24 * 60 * 60); + // Calculate hash + CDataStream ss(SER_GETHASH, 0); + ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << txPrev.nTime << txin.prevout.n << nTime; + if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay) + return true; + else + return DoS(100, error("CheckProofOfStake() : check target failed on coinstake %s", GetHash().ToString().c_str())); +} + +// ppcoin: total coin age spent in transaction, in the unit of coin-days. +// Only those coins meeting minimum age requirement counts. As those +// transactions not in main chain are not currently indexed so we +// might not find out about their coin age. Older transactions are +// guaranteed to be in main chain by sync-checkpoint. This rule is +// introduced to help nodes establish a consistent view of the coin +// age (trust score) of competing branches. +bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const +{ + CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds + nCoinAge = 0; + + if (IsCoinBase()) + return true; + + BOOST_FOREACH(const CTxIn& txin, vin) + { + // First try finding the previous transaction in database + CTransaction txPrev; + CTxIndex txindex; + if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) + continue; // previous transaction not in main chain + if (nTime < txPrev.nTime) + return false; // Transaction timestamp violation + + // Read block header + CBlock block; + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + return false; // unable to read block of previous transaction + if (block.GetBlockTime() + STAKE_MIN_AGE > nTime) + continue; // only count coins meeting min age requirement + + int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT; + + if (fDebug && GetBoolArg("-printcoinage")) + printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); + } + + CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60); + if (fDebug && GetBoolArg("-printcoinage")) + printf("coin age bnCoinDay=%s\n", bnCoinDay.ToString().c_str()); + nCoinAge = bnCoinDay.getuint64(); + return true; +} + +// ppcoin: total coin age spent in block, in the unit of coin-days. +bool CBlock::GetCoinAge(uint64& nCoinAge) const +{ + nCoinAge = 0; + + CTxDB txdb("r"); + BOOST_FOREACH(const CTransaction& tx, vtx) + { + uint64 nTxCoinAge; + if (tx.GetCoinAge(txdb, nTxCoinAge)) + nCoinAge += nTxCoinAge; + else + return false; + } + + if (nCoinAge == 0) // block coin age minimum 1 coin-day + nCoinAge = 1; + if (fDebug && GetBoolArg("-printcoinage")) + printf("block coin age total nCoinDays=%"PRI64d"\n", nCoinAge); + return true; +} + + bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { // Check for duplicate @@ -1616,6 +1805,9 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) if (!pindexNew) return error("AddToBlockIndex() : new CBlockIndex failed"); map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; + if (pindexNew->fProofOfStake) + setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); + pindexNew->phashBlock = &((*mi).first); map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) @@ -1623,7 +1815,12 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; } - pindexNew->bnChainWork = (pindexNew->pprev ? pindexNew->pprev->bnChainWork : 0) + pindexNew->GetBlockWork(); + + // ppcoin: compute chain trust score + uint64 nCoinAge; + if (!GetCoinAge(nCoinAge)) + return error("AddToBlockIndex() : invalid transaction in block"); + pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge; CTxDB txdb; if (!txdb.TxnBegin()) @@ -1633,7 +1830,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) return false; // New best - if (pindexNew->bnChainWork > bnBestChainWork) + if (pindexNew->nChainTrust > nBestChainTrust) if (!SetBestChain(txdb, pindexNew)) return false; @@ -1664,11 +1861,11 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : size limits failed")); // Check proof of work matches claimed amount - if (!CheckProofOfWork(GetHash(), nBits)) + if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) return DoS(50, error("CheckBlock() : proof of work failed")); // Check timestamp - if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) + if (GetBlockTime() > GetAdjustedTime() + nMaxClockDrift) return error("CheckBlock() : block timestamp too far in the future"); // First transaction must be coinbase, the rest must not be @@ -1678,10 +1875,32 @@ bool CBlock::CheckBlock() const if (vtx[i].IsCoinBase()) return DoS(100, error("CheckBlock() : more than one coinbase")); + // ppcoin: only the second transaction can be the optional coinstake + for (int i = 2; i < vtx.size(); i++) + if (vtx[i].IsCoinStake()) + return DoS(100, error("CheckBlock() : coinstake in wrong position")); + + // ppcoin: coinbase output should be empty if proof-of-stake block + if (IsProofOfStake() && !vtx[0].vout[0].IsEmpty()) + return error("CheckBlock() : coinbase output not empty for proof-of-stake block"); + + // Check coinbase timestamp + if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift) + return DoS(50, error("CheckBlock() : coinbase timestamp is too early")); + + // Check coinstake timestamp + if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift) + return DoS(50, error("CheckBlock() : coinstake timestamp is too early")); + // Check transactions BOOST_FOREACH(const CTransaction& tx, vtx) + { if (!tx.CheckTransaction()) return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed")); + // ppcoin: check transaction timestamp + if (GetBlockTime() < (int64)tx.nTime) + return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp")); + } // Check for duplicate txids. This is caught by ConnectInputs(), // but catching it earlier avoids a potential DoS attack: @@ -1705,6 +1924,10 @@ bool CBlock::CheckBlock() const if (hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); + // ppcoin: check block signature + if (!CheckBlockSignature()) + return DoS(100, error("CheckBlock() : bad block signature")); + return true; } @@ -1722,12 +1945,12 @@ bool CBlock::AcceptBlock() CBlockIndex* pindexPrev = (*mi).second; int nHeight = pindexPrev->nHeight+1; - // Check proof of work - if (nBits != GetNextWorkRequired(pindexPrev, this)) - return DoS(100, error("AcceptBlock() : incorrect proof of work")); + // Check proof-of-work or proof-of-stake + if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake())) + return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake")); // Check timestamp against prev - if (GetBlockTime() <= pindexPrev->GetMedianTimePast()) + if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime()) return error("AcceptBlock() : block's timestamp is too early"); // Check that all transactions are finalized @@ -1735,9 +1958,13 @@ bool CBlock::AcceptBlock() if (!tx.IsFinal(nHeight, GetBlockTime())) return DoS(10, error("AcceptBlock() : contains a non-final transaction")); - // Check that the block chain matches the known block chain up to a checkpoint - if (!Checkpoints::CheckBlock(nHeight, hash)) - return DoS(100, error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight)); + // Check that the block chain matches the known block chain up to a hardened checkpoint + if (!Checkpoints::CheckHardened(nHeight, hash)) + return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight)); + + // ppcoin: check that the block satisfies synchronized checkpoint + if (!Checkpoints::CheckSync(hash, pindexPrev)) + return error("AcceptBlock() : rejected by synchronized checkpoint"); // Write block to history file if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION))) @@ -1759,6 +1986,9 @@ bool CBlock::AcceptBlock() pnode->PushInventory(CInv(MSG_BLOCK, hash)); } + // ppcoin: check pending sync-checkpoint + Checkpoints::AcceptPendingSyncCheckpoint(); + return true; } @@ -1771,45 +2001,68 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) if (mapOrphanBlocks.count(hash)) return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str()); + // ppcoin: check proof-of-stake + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str()); + // Preliminary checks if (!pblock->CheckBlock()) return error("ProcessBlock() : CheckBlock FAILED"); - CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); - if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) + // ppcoin: verify hash target and signature of coinstake tx + if (pblock->IsProofOfStake() && !pblock->vtx[1].CheckProofOfStake(pblock->nBits)) + return error("ProcessBlock() : check proof-of-stake failed for block %s", hash.ToString().c_str()); + + CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint(); + if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) { // Extra checks to prevent "fill up memory by spamming with bogus blocks" int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; - if (deltaTime < 0) - { - if (pfrom) - pfrom->Misbehaving(100); - return error("ProcessBlock() : block with timestamp before last checkpoint"); - } CBigNum bnNewBlock; bnNewBlock.SetCompact(pblock->nBits); CBigNum bnRequired; - bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime)); + bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, pblock->IsProofOfStake())->nBits, deltaTime)); + if (bnNewBlock > bnRequired) { if (pfrom) pfrom->Misbehaving(100); - return error("ProcessBlock() : block with too little proof-of-work"); + return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work"); } } + // ppcoin: ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + Checkpoints::AskForPendingSyncCheckpoint(pfrom); // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str()); CBlock* pblock2 = new CBlock(*pblock); + // ppcoin: check proof-of-stake + if (pblock2->IsProofOfStake()) + { + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str()); + else + setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); + } mapOrphanBlocks.insert(make_pair(hash, pblock2)); mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); // Ask this guy to fill in what we're missing if (pfrom) + { pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2)); + // ppcoin: getblocks may not obtain the ancestor block rejected + // earlier by duplicate-stake check so we ask for it again directly + pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2))); + } return true; } @@ -1831,6 +2084,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) if (pblockOrphan->AcceptBlock()) vWorkQueue.push_back(pblockOrphan->GetHash()); mapOrphanBlocks.erase(pblockOrphan->GetHash()); + setStakeSeenOrphan.erase(pblockOrphan->GetProofOfStake()); delete pblockOrphan; } mapOrphanBlocksByPrev.erase(hashPrev); @@ -1840,7 +2094,53 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) return true; } +// ppcoin: sign block +bool CBlock::SignBlock(const CKeyStore& keystore) +{ + vector vSolutions; + txnouttype whichType; + const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0]; + if (!Solver(txout.scriptPubKey, whichType, vSolutions)) + return false; + if (whichType == TX_PUBKEY) + { + // Sign + const valtype& vchPubKey = vSolutions[0]; + CKey key; + if (!keystore.GetKey(Hash160(vchPubKey), key)) + return false; + if (key.GetPubKey() != vchPubKey) + return false; + return key.Sign(GetHash(), vchBlockSig); + } + return false; +} + +// ppcoin: check block signature +bool CBlock::CheckBlockSignature() const +{ + if (GetHash() == hashGenesisBlock) + return vchBlockSig.empty(); + + vector vSolutions; + txnouttype whichType; + const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0]; + + if (!Solver(txout.scriptPubKey, whichType, vSolutions)) + return false; + if (whichType == TX_PUBKEY) + { + const valtype& vchPubKey = vSolutions[0]; + CKey key; + if (!key.SetPubKey(vchPubKey)) + return false; + if (vchBlockSig.empty()) + return false; + return key.Verify(GetHash(), vchBlockSig); + } + return false; +} @@ -1910,7 +2210,7 @@ bool LoadBlockIndex(bool fAllowNew) { if (fTestNet) { - hashGenesisBlock = uint256("0x00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); + hashGenesisBlock = hashGenesisBlockTestNet; bnProofOfWorkLimit = CBigNum(~uint256(0) >> 28); pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0xbf; @@ -1942,21 +2242,21 @@ bool LoadBlockIndex(bool fAllowNew) // vMerkleTree: 4a5e1e // Genesis block - const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; + const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high"; CTransaction txNew; + txNew.nTime = 1339538219; txNew.vin.resize(1); txNew.vout.resize(1); txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); - txNew.vout[0].nValue = 50 * COIN; - txNew.vout[0].scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + txNew.vout[0].SetEmpty(); CBlock block; block.vtx.push_back(txNew); block.hashPrevBlock = 0; block.hashMerkleRoot = block.BuildMerkleTree(); block.nVersion = 1; - block.nTime = 1231006505; - block.nBits = 0x1d00ffff; - block.nNonce = 2083236893; + block.nTime = 1339540307; + block.nBits = bnProofOfWorkLimit.GetCompact(); + block.nNonce = 1281822831; if (fTestNet) { @@ -1969,9 +2269,10 @@ bool LoadBlockIndex(bool fAllowNew) printf("%s\n", block.GetHash().ToString().c_str()); printf("%s\n", hashGenesisBlock.ToString().c_str()); printf("%s\n", block.hashMerkleRoot.ToString().c_str()); - assert(block.hashMerkleRoot == uint256("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); + assert(block.hashMerkleRoot == uint256("0x1557f46a17fcf8843dbe4c0c0edfd1d17eeff2c3c48d73a59d11f5d176e4b54d")); block.print(); assert(block.GetHash() == hashGenesisBlock); + assert(block.CheckBlock()); // Start new block file unsigned int nFile; @@ -1980,6 +2281,28 @@ bool LoadBlockIndex(bool fAllowNew) return error("LoadBlockIndex() : writing genesis block to disk failed"); if (!block.AddToBlockIndex(nFile, nBlockPos)) return error("LoadBlockIndex() : genesis block not accepted"); + + // ppcoin: initialize synchronized checkpoint + if (!Checkpoints::WriteSyncCheckpoint(hashGenesisBlock)) + return error("LoadBlockIndex() : failed to init sync checkpoint"); + } + + // ppcoin: if checkpoint master key changed must reset sync-checkpoint + { + CTxDB txdb; + string strPubKey = ""; + if (!txdb.ReadCheckpointPubKey(strPubKey) || strPubKey != CSyncCheckpoint::strMasterPubKey) + { + // write checkpoint master key to db + txdb.TxnBegin(); + if (!txdb.WriteCheckpointPubKey(CSyncCheckpoint::strMasterPubKey)) + return error("LoadBlockIndex() : failed to write new checkpoint master key to db"); + if (!txdb.TxnCommit()) + return error("LoadBlockIndex() : failed to commit new checkpoint master key to db"); + if (!Checkpoints::ResetSyncCheckpoint()) + return error("LoadBlockIndex() : failed to reset sync-checkpoint"); + } + txdb.Close(); } return true; @@ -2032,11 +2355,12 @@ void PrintBlockTree() // print item CBlock block; block.ReadFromDisk(pindex); - printf("%d (%u,%u) %s %s tx %d", + printf("%d (%u,%u) %s %08lx %s tx %d", pindex->nHeight, pindex->nFile, pindex->nBlockPos, block.GetHash().ToString().substr(0,20).c_str(), + block.nBits, DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), block.vtx.size()); @@ -2092,12 +2416,18 @@ string GetWarnings(string strFor) } // Longer invalid proof-of-work chain - if (pindexBest && bnBestInvalidWork > bnBestChainWork + pindexBest->GetBlockWork() * 6) + if (pindexBest && nBestInvalidTrust > nBestChainTrust + pindexBest->GetBlockTrust() * 6) { nPriority = 2000; strStatusBar = strRPC = "WARNING: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."; } + if (Checkpoints::hashInvalidCheckpoint != 0) + { + nPriority = 3000; + strStatusBar = strRPC = "WARNING: Invalid checkpoint found! Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade."; + } + // Alerts { LOCK(cs_mapAlerts); @@ -2271,6 +2601,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return true; } + // ppcoin: record my external IP reported by peer + if (addrFrom.IsRoutable() && addrMe.IsRoutable()) + addrSeenByPeer = addrMe; + // Be shy and don't send version until we hear if (pfrom->fInbound) pfrom->PushVersion(); @@ -2327,11 +2661,22 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) item.second.RelayTo(pfrom); } + // ppcoin: relay sync-checkpoint + { + LOCK(Checkpoints::cs_hashSyncCheckpoint); + if (!Checkpoints::checkpointMessage.IsNull()) + Checkpoints::checkpointMessage.RelayTo(pfrom); + } + pfrom->fSuccessfullyConnected = true; printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight); cPeerBlockCounts.input(pfrom->nStartingHeight); + + // ppcoin: ask for pending sync-checkpoint if any + if (!IsInitialBlockDownload()) + Checkpoints::AskForPendingSyncCheckpoint(pfrom); } @@ -2774,6 +3119,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } } + else if (strCommand == "checkpoint") + { + CSyncCheckpoint checkpoint; + vRecv >> checkpoint; + + if (checkpoint.ProcessSyncCheckpoint(pfrom)) + { + // Relay + pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + } else { @@ -3180,9 +3539,9 @@ public: uint64 nLastBlockTx = 0; uint64 nLastBlockSize = 0; -CBlock* CreateNewBlock(CReserveKey& reservekey) +CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfWorkOnly) { - CBlockIndex* pindexPrev = pindexBest; + CReserveKey reservekey(pwallet); // Create new block auto_ptr pblock(new CBlock()); @@ -3199,6 +3558,37 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) // Add our coinbase tx as first transaction pblock->vtx.push_back(txNew); + // ppcoin: if coinstake available add coinstake tx + static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift + 60; // only initialized at startup + CBlockIndex* pindexPrev = pindexBest; + + if (!fProofOfWorkOnly) + { + while (nLastCoinStakeCheckTime < GetAdjustedTime()) + { + pindexPrev = pindexBest; // get best block again to avoid getting stale + pblock->nBits = GetNextTargetRequired(pindexPrev, true); + CTransaction txCoinStake; + { + static CCriticalSection cs; + LOCK(cs); + // mining may have been suspended for a while so + // need to take max to satisfy the timestamp protocol + nLastCoinStakeCheckTime++; + nLastCoinStakeCheckTime = max(nLastCoinStakeCheckTime, (unsigned int) (GetAdjustedTime() - nMaxClockDrift + 60)); + txCoinStake.nTime = nLastCoinStakeCheckTime; + } + if (pwallet->CreateCoinStake(pblock->nBits, txCoinStake)) + { + pblock->vtx.push_back(txCoinStake); + pblock->vtx[0].vout[0].SetEmpty(); + break; + } + } + } + + pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake()); + // Collect memory pool transactions into the block int64 nFees = 0; { @@ -3212,7 +3602,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) for (map::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) { CTransaction& tx = (*mi).second; - if (tx.IsCoinBase() || !tx.IsFinal()) + if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal()) continue; COrphan* porphan = NULL; @@ -3271,7 +3661,6 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) while (!mapPriority.empty()) { // Take highest priority transaction off priority queue - double dPriority = -(*mapPriority.begin()).first; CTransaction& tx = *(*mapPriority.begin()).second; mapPriority.erase(mapPriority.begin()); @@ -3285,9 +3674,12 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; - // Transaction fee required depends on block size - bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority)); - int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree, GMF_BLOCK); + // Timestamp limit + if (tx.nTime > GetAdjustedTime()) + continue; + + // ppcoin: simplify transaction fee - allow free = false + int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK); // Connecting shouldn't fail due to dependency on other memory pool transactions // because we're already processing them in order of dependency @@ -3305,7 +3697,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; - if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true)) + if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true)) continue; mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size()); swap(mapTestPool, mapTestPoolTmp); @@ -3338,13 +3730,16 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) printf("CreateNewBlock(): total size %lu\n", nBlockSize); } - pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); + if (pblock->IsProofOfWork()) + pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime()); + pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); pblock->UpdateTime(pindexPrev); - pblock->nBits = GetNextWorkRequired(pindexPrev, pblock.get()); pblock->nNonce = 0; return pblock.release(); @@ -3419,12 +3814,12 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey) uint256 hash = pblock->GetHash(); uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - if (hash > hashTarget) - return false; + if (hash > hashTarget && pblock->IsProofOfWork()) + return error("BitcoinMiner : proof-of-work not meeting target"); //// debug print printf("BitcoinMiner:\n"); - printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); + printf("new block found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); pblock->print(); printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str()); printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str()); @@ -3487,11 +3882,27 @@ void static BitcoinMiner(CWallet *pwallet) unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; CBlockIndex* pindexPrev = pindexBest; - auto_ptr pblock(CreateNewBlock(reservekey)); + auto_ptr pblock(CreateNewBlock(pwallet)); if (!pblock.get()) return; + IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce); + // ppcoin: if proof-of-stake block found then process block + if (pblock->IsProofOfStake()) + { + if (!pblock->SignBlock(*pwalletMain)) + { + error("BitcoinMiner: Unable to sign new proof-of-stake block"); + return; + } + printf("BitcoinMiner : proof-of-stake block found %s\n", pblock->GetHash().ToString().c_str()); + SetThreadPriority(THREAD_PRIORITY_NORMAL); + CheckWork(pblock.get(), *pwalletMain, reservekey); + SetThreadPriority(THREAD_PRIORITY_LOWEST); + continue; + } + printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); @@ -3505,7 +3916,6 @@ void static BitcoinMiner(CWallet *pwallet) FormatHashBuffers(pblock.get(), pmidstate, pdata, phash1); unsigned int& nBlockTime = *(unsigned int*)(pdata + 64 + 4); - unsigned int& nBlockBits = *(unsigned int*)(pdata + 64 + 8); unsigned int& nBlockNonce = *(unsigned int*)(pdata + 64 + 12); @@ -3536,6 +3946,11 @@ void static BitcoinMiner(CWallet *pwallet) // Found a solution pblock->nNonce = ByteReverse(nNonceFound); assert(hash == pblock->GetHash()); + if (!pblock->SignBlock(*pwalletMain)) + { + error("BitcoinMiner: Unable to sign new proof-of-work block"); + return; + } SetThreadPriority(THREAD_PRIORITY_NORMAL); CheckWork(pblock.get(), *pwalletMain, reservekey); @@ -3591,14 +4006,13 @@ void static BitcoinMiner(CWallet *pwallet) break; // Update nTime every few seconds + pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime()); + pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); pblock->UpdateTime(pindexPrev); nBlockTime = ByteReverse(pblock->nTime); - if (fTestNet) - { - // Changing pblock->nTime can change work required on testnet: - nBlockBits = ByteReverse(pblock->nBits); - hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - } + if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift) + break; // need to update coinbase timestamp } } } diff --git a/src/main.h b/src/main.h index 241ef46..b1d136f 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_MAIN_H @@ -20,6 +21,7 @@ class CBlock; class CBlockIndex; class CKeyItem; class CReserveKey; +class COutPoint; class CAddress; class CInv; @@ -30,9 +32,9 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000; static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2; static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50; static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100; -static const int64 MIN_TX_FEE = 50000; +static const int64 MIN_TX_FEE = 10000; static const int64 MIN_RELAY_TX_FEE = 10000; -static const int64 MAX_MONEY = 21000000 * COIN; +static const int64 MAX_MONEY = 2000000000 * COIN; inline bool MoneyRange(int64 nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } static const int COINBASE_MATURITY = 100; // Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. @@ -43,6 +45,8 @@ static const int fHaveUPnP = true; static const int fHaveUPnP = false; #endif +static const uint256 hashGenesisBlockOfficial("0x000000007c82d1f0aa2896b01bf533a8cc26a1f44790be4ceb4ecde7bee24add"); +static const uint256 hashGenesisBlockTestNet("0x00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); extern CScript COINBASE_FLAGS; @@ -53,11 +57,12 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern std::map mapBlockIndex; +extern std::set > setStakeSeen; extern uint256 hashGenesisBlock; extern CBlockIndex* pindexGenesisBlock; extern int nBestHeight; -extern CBigNum bnBestChainWork; -extern CBigNum bnBestInvalidWork; +extern uint64 nBestChainTrust; +extern uint64 nBestInvalidTrust; extern uint256 hashBestChain; extern CBlockIndex* pindexBest; extern unsigned int nTransactionsUpdated; @@ -69,9 +74,11 @@ extern int64 nHPSTimerStart; extern int64 nTimeBestReceived; extern CCriticalSection cs_setpwalletRegistered; extern std::set setpwalletRegistered; +extern std::map mapOrphanBlocks; // Settings extern int64 nTransactionFee; +extern int64 nBalanceReserve; @@ -92,16 +99,19 @@ void PrintBlockTree(); bool ProcessMessages(CNode* pfrom); bool SendMessages(CNode* pto, bool fSendTrickle); void GenerateBitcoins(bool fGenerate, CWallet* pwallet); -CBlock* CreateNewBlock(CReserveKey& reservekey); +CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfWorkOnly=false); void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce); void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1); bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey); bool CheckProofOfWork(uint256 hash, unsigned int nBits); +int64 GetProofOfStakeReward(int64 nCoinAge); unsigned int ComputeMinWork(unsigned int nBase, int64 nTime); int GetNumBlocksOfPeers(); bool IsInitialBlockDownload(); std::string GetWarnings(std::string strFor); - +bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew); +uint256 WantedByOrphan(const CBlock* pblockOrphan); +const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake); @@ -339,6 +349,17 @@ public: return (nValue == -1); } + void SetEmpty() + { + nValue = 0; + scriptPubKey.clear(); + } + + bool IsEmpty() const + { + return (nValue == 0 && scriptPubKey.empty()); + } + uint256 GetHash() const { return SerializeHash(*this); @@ -357,9 +378,10 @@ public: std::string ToString() const { + if (IsEmpty()) return "CTxOut(empty)"; if (scriptPubKey.size() < 6) return "CTxOut(error)"; - return strprintf("CTxOut(nValue=%"PRI64d".%08"PRI64d", scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,30).c_str()); + return strprintf("CTxOut(nValue=%s, scriptPubKey=%s)", FormatMoney(nValue).c_str(), scriptPubKey.ToString().substr(0,30).c_str()); } void print() const @@ -387,6 +409,7 @@ class CTransaction { public: int nVersion; + unsigned int nTime; std::vector vin; std::vector vout; unsigned int nLockTime; @@ -404,6 +427,7 @@ public: ( READWRITE(this->nVersion); nVersion = this->nVersion; + READWRITE(nTime); READWRITE(vin); READWRITE(vout); READWRITE(nLockTime); @@ -412,6 +436,7 @@ public: void SetNull() { nVersion = 1; + nTime = GetAdjustedTime(); vin.clear(); vout.clear(); nLockTime = 0; @@ -476,7 +501,13 @@ public: bool IsCoinBase() const { - return (vin.size() == 1 && vin[0].prevout.IsNull()); + return (vin.size() == 1 && vin[0].prevout.IsNull() && vout.size() == 1); + } + + bool IsCoinStake() const + { + // ppcoin: the coin stake transaction is marked with the first output empty + return (vin.size() > 0 && vout.size() == 2 && vout[0].IsEmpty()); } /** Check for standard transaction types @@ -537,7 +568,7 @@ public: return dPriority > COIN * 144 / 250; } - int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=true, enum GetMinFee_mode mode=GMF_BLOCK) const + int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK) const { // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; @@ -615,6 +646,7 @@ public: friend bool operator==(const CTransaction& a, const CTransaction& b) { return (a.nVersion == b.nVersion && + a.nTime == b.nTime && a.vin == b.vin && a.vout == b.vout && a.nLockTime == b.nLockTime); @@ -629,8 +661,10 @@ public: std::string ToString() const { std::string str; - str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n", + str += IsCoinBase()? "Coinbase" : (IsCoinStake()? "Coinstake" : "CTransaction"); + str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n", GetHash().ToString().substr(0,10).c_str(), + nTime, nVersion, vin.size(), vout.size(), @@ -678,12 +712,14 @@ public: @param[in] fStrictPayToScriptHash true if fully validating p2sh transactions @return Returns true if all checks succeed */ - bool ConnectInputs(MapPrevTx inputs, + bool ConnectInputs(CTxDB& txdb, MapPrevTx inputs, std::map& mapTestPool, const CDiskTxPos& posThisTx, const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash=true); bool ClientConnectInputs(); bool CheckTransaction() const; bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); + bool GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const; // ppcoin: get transaction coin age + bool CheckProofOfStake(unsigned int nBits) const; protected: const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const; @@ -827,6 +863,9 @@ public: // network and disk std::vector vtx; + // ppcoin: block signature - signed by coin base txout[0]'s owner + std::vector vchBlockSig; + // memory only mutable std::vector vMerkleTree; @@ -849,11 +888,17 @@ public: READWRITE(nBits); READWRITE(nNonce); - // ConnectBlock depends on vtx being last so it can calculate offset + // ConnectBlock depends on vtx following header to generate CDiskTxPos if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY))) + { READWRITE(vtx); + READWRITE(vchBlockSig); + } else if (fRead) + { const_cast(this)->vtx.clear(); + const_cast(this)->vchBlockSig.clear(); + } ) void SetNull() @@ -865,6 +910,7 @@ public: nBits = 0; nNonce = 0; vtx.clear(); + vchBlockSig.clear(); vMerkleTree.clear(); nDoS = 0; } @@ -886,6 +932,30 @@ public: void UpdateTime(const CBlockIndex* pindexPrev); + // ppcoin: two types of block: proof-of-work or proof-of-stake + bool IsProofOfStake() const + { + return (vtx.size() > 1 && vtx[1].IsCoinStake()); + } + + bool IsProofOfWork() const + { + return !IsProofOfStake(); + } + + std::pair GetProofOfStake() const + { + return IsProofOfStake()? std::make_pair(vtx[1].vin[0].prevout, vtx[1].nTime) : std::make_pair(COutPoint(), (unsigned int)0); + } + + // ppcoin: get max transaction timestamp + int64 GetMaxTransactionTime() const + { + int64 maxTransactionTime = 0; + BOOST_FOREACH(const CTransaction& tx, vtx) + maxTransactionTime = std::max(maxTransactionTime, (int64)tx.nTime); + return maxTransactionTime; + } uint256 BuildMerkleTree() const { @@ -990,7 +1060,7 @@ public: } // Check the header - if (!CheckProofOfWork(GetHash(), nBits)) + if (fReadTransactions && IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) return error("CBlock::ReadFromDisk() : errors in block header"); return true; @@ -1000,13 +1070,14 @@ public: void print() const { - printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d)\n", + printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d, vchBlockSig=%s)\n", GetHash().ToString().substr(0,20).c_str(), nVersion, hashPrevBlock.ToString().substr(0,20).c_str(), hashMerkleRoot.ToString().substr(0,10).c_str(), nTime, nBits, nNonce, - vtx.size()); + vtx.size(), + HexStr(vchBlockSig.begin(), vchBlockSig.end()).c_str()); for (unsigned int i = 0; i < vtx.size(); i++) { printf(" "); @@ -1026,6 +1097,9 @@ public: bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos); bool CheckBlock() const; bool AcceptBlock(); + bool GetCoinAge(uint64& nCoinAge) const; // ppcoin: calculate total coin age spent in block + bool SignBlock(const CKeyStore& keystore); + bool CheckBlockSignature() const; private: bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew); @@ -1051,8 +1125,11 @@ public: CBlockIndex* pnext; unsigned int nFile; unsigned int nBlockPos; + uint64 nChainTrust;// ppcoin: trust score of chain, in the unit of coin-days int nHeight; - CBigNum bnChainWork; + bool fProofOfStake; // ppcoin: is the block of proof-of-stake type + COutPoint prevoutStake; + unsigned int nStakeTime; // block header int nVersion; @@ -1070,7 +1147,10 @@ public: nFile = 0; nBlockPos = 0; nHeight = 0; - bnChainWork = 0; + nChainTrust = 0; + fProofOfStake = true; + prevoutStake.SetNull(); + nStakeTime = 0; nVersion = 0; hashMerkleRoot = 0; @@ -1087,7 +1167,18 @@ public: nFile = nFileIn; nBlockPos = nBlockPosIn; nHeight = 0; - bnChainWork = 0; + nChainTrust = 0; + fProofOfStake = block.IsProofOfStake(); + if (fProofOfStake) + { + prevoutStake = block.vtx[1].vin[0].prevout; + nStakeTime = block.vtx[1].nTime; + } + else + { + prevoutStake.SetNull(); + nStakeTime = 0; + } nVersion = block.nVersion; hashMerkleRoot = block.hashMerkleRoot; @@ -1119,13 +1210,9 @@ public: return (int64)nTime; } - CBigNum GetBlockWork() const + int64 GetBlockTrust() const { - CBigNum bnTarget; - bnTarget.SetCompact(nBits); - if (bnTarget <= 0) - return 0; - return (CBigNum(1)<<256) / (bnTarget+1); + return (nChainTrust - (pprev? pprev->nChainTrust : 0)); } bool IsInMainChain() const @@ -1135,7 +1222,7 @@ public: bool CheckIndex() const { - return CheckProofOfWork(GetBlockHash(), nBits); + return IsProofOfWork() ? CheckProofOfWork(GetBlockHash(), nBits) : true; } bool EraseBlockFromDisk() @@ -1181,12 +1268,21 @@ public: return pindex->GetMedianTimePast(); } + bool IsProofOfWork() const + { + return !fProofOfStake; + } + bool IsProofOfStake() const + { + return fProofOfStake; + } std::string ToString() const { - return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)", - pprev, pnext, nFile, nBlockPos, nHeight, + return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nChainTrust=%"PRI64d" nHeight=%d, fProofOfStake=%d prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)", + pprev, pnext, nFile, nBlockPos, nChainTrust, nHeight, + fProofOfStake, prevoutStake.ToString().c_str(), nStakeTime, hashMerkleRoot.ToString().substr(0,10).c_str(), GetBlockHash().ToString().substr(0,20).c_str()); } @@ -1226,7 +1322,19 @@ public: READWRITE(hashNext); READWRITE(nFile); READWRITE(nBlockPos); + READWRITE(nChainTrust); READWRITE(nHeight); + READWRITE(fProofOfStake); + if (fProofOfStake) + { + READWRITE(prevoutStake); + READWRITE(nStakeTime); + } + else if (fRead) + { + const_cast(this)->prevoutStake.SetNull(); + const_cast(this)->nStakeTime = 0; + } // block header READWRITE(this->nVersion); @@ -1595,7 +1703,7 @@ public: bool CheckSignature() { CKey key; - if (!key.SetPubKey(ParseHex("04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284"))) + if (!key.SetPubKey(ParseHex("0487ca85b6ae9d311f996c7616d20d0c88a5b4f07d25e78f419019f35cce6522acf978b2d99f0e7a58db1f120439e5c1889266927854aa57c93956c2569188a539"))) return error("CAlert::CheckSignature() : SetPubKey failed"); if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) return error("CAlert::CheckSignature() : verify signature failed"); diff --git a/src/makefile.mingw b/src/makefile.mingw index 37201e3..98ed2cd 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -1,7 +1,54 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto +# Copyright (c) 2012 The PPCoin developers # Distributed under the MIT/X11 software license, see the accompanying # file license.txt or http://www.opensource.org/licenses/mit-license.php. +# Windows commandline build procedure: +# - Install MinGW following http://www.mingw.org/wiki/Getting_Started. +# Install with the C++ and MSYS options checked +# +# - Add/edit 'MAKE' environment variable with value '/c/MinGW32/bin/mingw32-make.exe' +# +# - Build openssl library version: 1.0.1b +# download from http://www.openssl.org/source/ +# Extract to c:\openssl-1.0.1b-mgw +# In MinGW MSYS: +# ./config +# make +# +# - Build Berkeley DB library version: 4.8.30.NC +# download from http://www.oracle.com/technology/software/products/berkeley-db/index.html +# Extract to c:\db-4.8.30.NC-mgw +# In MinGW MSYS: +# cd build_unix +# sh ../dist/configure --disable-replication --enable-mingw --enable-cxx --prefix=/usr/local +# Edit db.h@113 in build_unix +# from +# typedef pthread_t db_threadid_t; +# to +# typedef u_int32_t db_threadid_t; +# Then +# make +# +# - Build Boost C++ library version: 1.47.0 +# download from http://www.boost.org/users/download/ +# Extract to c:\boost-1.47.0-mgw +# Install Boost.Build: +# cd tools\build\v2 +# bootstrap.bat +# b2 install --prefix=BOOST_BUILD_INSTALL_DIR +# Add BOOST_BUILD_INSTALL_DIR to your PATH system environment variable +# Build boost library in MSDOS: +# cd c:\boost-1.47.0-mgw +# bjam toolset=gcc --build-type=complete stage +# +# - Build ppcoind.exe +# in MinGW MSYS +# cd ppcoin/src +# make ppcoind.exe -f makefile.mingw USE_UPNP= +# +# + USE_UPNP:=0 INCLUDEPATHS= \ @@ -15,10 +62,10 @@ LIBPATHS= \ -L"C:\openssl-1.0.1b-mgw" LIBS= \ - -l boost_system-mgw45-mt-s-1_47 \ - -l boost_filesystem-mgw45-mt-s-1_47 \ - -l boost_program_options-mgw45-mt-s-1_47 \ - -l boost_thread-mgw45-mt-s-1_47 \ + -l boost_system-mgw46-mt-s-1_47 \ + -l boost_filesystem-mgw46-mt-s-1_47 \ + -l boost_program_options-mgw46-mt-s-1_47 \ + -l boost_thread-mgw46-mt-s-1_47 \ -l db_cxx \ -l ssl \ -l crypto @@ -64,12 +111,12 @@ OBJS= \ obj/noui.o -all: bitcoind.exe +all: ppcoind.exe obj/%.o: %.cpp $(HEADERS) g++ -c $(CFLAGS) -o $@ $< -bitcoind.exe: $(OBJS:obj/%=obj/%) +ppcoind.exe: $(OBJS:obj/%=obj/%) g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) @@ -81,7 +128,7 @@ test_bitcoin.exe: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) clean: - -del /Q bitcoind test_bitcoin + -del /Q ppcoind test_bitcoin -del /Q obj\* -del /Q obj-test\* -del /Q build.h diff --git a/src/makefile.unix b/src/makefile.unix index 58331ca..cae7d44 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -1,4 +1,5 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto +# Copyright (c) 2011 The PPCoin developers # Distributed under the MIT/X11 software license, see the accompanying # file license.txt or http://www.opensource.org/licenses/mit-license.php. @@ -109,7 +110,7 @@ OBJS= \ obj/noui.o -all: bitcoind +all: ppcoind # auto-generated dependencies: -include obj/*.P @@ -127,8 +128,8 @@ obj/%.o: %.cpp -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) -bitcoind: $(OBJS:obj/%=obj/%) - $(CXX) $(xCXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) +ppcoind: $(OBJS:obj/%=obj/%) + $(CXX) $(xCXXFLAGS) -rdynamic -o $@ $^ $(LDFLAGS) $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) @@ -139,15 +140,25 @@ obj-test/%.o: test/%.cpp -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) -test_bitcoin: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) +test_ppcoin: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(LDFLAGS) $(LIBS) clean: - -rm -f bitcoind test_bitcoin + -rm -f ppcoind test_ppcoin genesis -rm -f obj/*.o -rm -f obj-test/*.o -rm -f obj/*.P -rm -f obj-test/*.P -rm -f src/build.h + -rm -f ppcoin/obj/* + +ppcoin/obj/genesis.o: ppcoin/genesis.cpp + $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< + $(CXX) -c $(xCXXFLAGS) -MMD -DPPCOIN_GENESIS -o obj/nogui/init.o init.cpp + +genesis: ppcoin/obj/genesis.o $(OBJS:obj/%=obj/nogui/%) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBS) + -rm -f obj/init.* + -rm -f ppcoin/obj/genesis.* FORCE: diff --git a/src/net.cpp b/src/net.cpp index d3236b3..5d5be71 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -47,6 +48,7 @@ bool fAllowDNS = false; static bool fUseUPnP = false; uint64 nLocalServices = (fClient ? 0 : NODE_NETWORK); CAddress addrLocalHost(CService("0.0.0.0", 0), nLocalServices); +CAddress addrSeenByPeer(CService("0.0.0.0", 0), nLocalServices); static CNode* pnodeLocalHost = NULL; uint64 nLocalHostNonce = 0; array vnThreadsRunning; @@ -248,8 +250,8 @@ bool GetMyExternalIP(CNetAddr& ipRet) void ThreadGetMyExternalIP(void* parg) { - // Wait for IRC to get it first - if (GetBoolArg("-irc", false)) + // Wait for IRC to get it first - disabled with ppcoin + if (false && GetBoolArg("-irc", false)) { for (int i = 0; i < 2 * 60; i++) { @@ -977,11 +979,10 @@ void MapPort(bool /* unused fMapPort */) // Each pair gives a source name and a seed name. // The first name is used as information source for addrman. // The second name should resolve to a list of seed addresses. +// testnet dns seed begins with 't', all else are ppcoin dns seeds. static const char *strDNSSeed[][2] = { - {"xf2.org", "bitseed.xf2.org"}, - {"bluematt.me", "dnsseed.bluematt.me"}, - {"bitcoin.sipa.be", "seed.bitcoin.sipa.be"}, - {"dashjr.org", "dnsseed.bitcoin.dashjr.org"}, + {"ppcseed", "ppcseed.zapto.org"}, + {"tncseed", "tncseed.zapto.org"}, }; void ThreadDNSAddressSeed(void* parg) @@ -1008,11 +1009,14 @@ void ThreadDNSAddressSeed2(void* parg) printf("ThreadDNSAddressSeed started\n"); int found = 0; - if (!fTestNet) + if (true /*!fTestNet*/) // ppcoin enables dns seeding with testnet too { printf("Loading addresses from DNS seeds (could take a while)\n"); for (unsigned int seed_idx = 0; seed_idx < ARRAYLEN(strDNSSeed); seed_idx++) { + if (fTestNet && strDNSSeed[seed_idx][1][0] != 't') continue; + if ((!fTestNet) && strDNSSeed[seed_idx][1][0] == 't') continue; + vector vaddr; vector vAdd; if (LookupHost(strDNSSeed[seed_idx][1], vaddr)) @@ -1046,83 +1050,7 @@ void ThreadDNSAddressSeed2(void* parg) unsigned int pnSeed[] = { - 0x959bd347, 0xf8de42b2, 0x73bc0518, 0xea6edc50, 0x21b00a4d, 0xc725b43d, 0xd665464d, 0x1a2a770e, - 0x27c93946, 0x65b2fa46, 0xb80ae255, 0x66b3b446, 0xb1877a3e, 0x6ee89e3e, 0xc3175b40, 0x2a01a83c, - 0x95b1363a, 0xa079ad3d, 0xe6ca801f, 0x027f4f4a, 0x34f7f03a, 0xf790f04a, 0x16ca801f, 0x2f4d5e40, - 0x3a4d5e40, 0xc43a322e, 0xc8159753, 0x14d4724c, 0x7919a118, 0xe0bdb34e, 0x68a16b2e, 0xff64b44d, - 0x6099115b, 0x9b57b05b, 0x7bd1b4ad, 0xdf95944f, 0x29d2b73d, 0xafa8db79, 0xe247ba41, 0x24078348, - 0xf722f03c, 0x33567ebc, 0xace64ed4, 0x984d3932, 0xb5f34e55, 0x27b7024d, 0x94579247, 0x8894042e, - 0x9357d34c, 0x1063c24b, 0xcaa228b1, 0xa3c5a8b2, 0x5dc64857, 0xa2c23643, 0xa8369a54, 0x31203077, - 0x00707c5c, 0x09fc0b3a, 0x272e9e2e, 0xf80f043e, 0x9449ca3e, 0x5512c33e, 0xd106b555, 0xe8024157, - 0xe288ec29, 0xc79c5461, 0xafb63932, 0xdb02ab4b, 0x0e512777, 0x8a145a4c, 0xb201ff4f, 0x5e09314b, - 0xcd9bfbcd, 0x1c023765, 0x4394e75c, 0xa728bd4d, 0x65331552, 0xa98420b1, 0x89ecf559, 0x6e80801f, - 0xf404f118, 0xefd62b51, 0x05918346, 0x9b186d5f, 0xacabab46, 0xf912e255, 0xc188ea62, 0xcc55734e, - 0xc668064d, 0xd77a4558, 0x46201c55, 0xf17dfc80, 0xf7142f2e, 0x87bfb718, 0x8aa54fb2, 0xc451d518, - 0xc4ae8831, 0x8dd44d55, 0x5bbd206c, 0x64536b5d, 0x5c667e60, 0x3b064242, 0xfe963a42, 0xa28e6dc8, - 0xe8a9604a, 0xc989464e, 0xd124a659, 0x50065140, 0xa44dfe5e, 0x1079e655, 0x3fb986d5, 0x47895b18, - 0x7d3ce4ad, 0x4561ba50, 0x296eec62, 0x255b41ad, 0xaed35ec9, 0x55556f12, 0xc7d3154d, 0x3297b65d, - 0x8930121f, 0xabf42e4e, 0x4a29e044, 0x1212685d, 0x676c1e40, 0xce009744, 0x383a8948, 0xa2dbd0ad, - 0xecc2564d, 0x07dbc252, 0x887ee24b, 0x5171644c, 0x6bb798c1, 0x847f495d, 0x4cbb7145, 0x3bb81c32, - 0x45eb262e, 0xc8015a4e, 0x250a361b, 0xf694f946, 0xd64a183e, 0xd4f1dd59, 0x8f20ffd4, 0x51d9e55c, - 0x09521763, 0x5e02002e, 0x32c8074d, 0xe685762e, 0x8290b0bc, 0x762a922e, 0xfc5ee754, 0x83a24829, - 0x775b224d, 0x6295bb4d, 0x38ec0555, 0xbffbba50, 0xe5560260, 0x86b16a7c, 0xd372234e, 0x49a3c24b, - 0x2f6a171f, 0x4d75ed60, 0xae94115b, 0xcb543744, 0x63080c59, 0x3f9c724c, 0xc977ce18, 0x532efb18, - 0x69dc3b2e, 0x5f94d929, 0x1732bb4d, 0x9c814b4d, 0xe6b3762e, 0xc024f662, 0x8face35b, 0x6b5b044d, - 0x798c7b57, 0x79a6b44c, 0x067d3057, 0xf9e94e5f, 0x91cbe15b, 0x71405eb2, 0x2662234e, 0xcbcc4a6d, - 0xbf69d54b, 0xa79b4e55, 0xec6d3e51, 0x7c0b3c02, 0x60f83653, 0x24c1e15c, 0x1110b62e, 0x10350f59, - 0xa56f1d55, 0x3509e7a9, 0xeb128354, 0x14268e2e, 0x934e28bc, 0x8e32692e, 0x8331a21f, 0x3e633932, - 0xc812b12e, 0xc684bf2e, 0x80112d2e, 0xe0ddc96c, 0xc630ca4a, 0x5c09b3b2, 0x0b580518, 0xc8e9d54b, - 0xd169aa43, 0x17d0d655, 0x1d029963, 0x7ff87559, 0xcb701f1f, 0x6fa3e85d, 0xe45e9a54, 0xf05d1802, - 0x44d03b2e, 0x837b692e, 0xccd4354e, 0x3d6da13c, 0x3423084d, 0xf707c34a, 0x55f6db3a, 0xad26e442, - 0x6233a21f, 0x09e80e59, 0x8caeb54d, 0xbe870941, 0xb407d20e, 0x20b51018, 0x56fb152e, 0x460d2a4e, - 0xbb9a2946, 0x560eb12e, 0xed83dd29, 0xd6724f53, 0xa50aafb8, 0x451346d9, 0x88348e2e, 0x7312fead, - 0x8ecaf96f, 0x1bda4e5f, 0xf1671e40, 0x3c8c3e3b, 0x4716324d, 0xdde24ede, 0xf98cd17d, 0xa91d4644, - 0x28124eb2, 0x147d5129, 0xd022042e, 0x61733d3b, 0xad0d5e02, 0x8ce2932e, 0xe5c18502, 0x549c1e32, - 0x9685801f, 0x86e217ad, 0xd948214b, 0x4110f462, 0x3a2e894e, 0xbd35492e, 0x87e0d558, 0x64b8ef7d, - 0x7c3eb962, 0x72a84b3e, 0x7cd667c9, 0x28370a2e, 0x4bc60e7b, 0x6fc1ec60, 0x14a6983f, 0x86739a4b, - 0x46954e5f, 0x32e2e15c, 0x2e9326cf, 0xe5801c5e, 0x379607b2, 0x32151145, 0xf0e39744, 0xacb54c55, - 0xa37dfb60, 0x83b55cc9, 0x388f7ca5, 0x15034f5f, 0x3e94965b, 0x68e0ffad, 0x35280f59, 0x8fe190cf, - 0x7c6ba5b2, 0xa5e9db43, 0x4ee1fc60, 0xd9d94e5f, 0x04040677, 0x0ea9b35e, 0x5961f14f, 0x67fda063, - 0xa48a5a31, 0xc6524e55, 0x283d325e, 0x3f37515f, 0x96b94b3e, 0xacce620e, 0x6481cc5b, 0xa4a06d4b, - 0x9e95d2d9, 0xe40c03d5, 0xc2f4514b, 0xb79aad44, 0xf64be843, 0xb2064070, 0xfca00455, 0x429dfa4e, - 0x2323f173, 0xeda4185e, 0xabd5227d, 0x9efd4d58, 0xb1104758, 0x4811e955, 0xbd9ab355, 0xe921f44b, - 0x9f166dce, 0x09e279b2, 0xe0c9ac7b, 0x7901a5ad, 0xa145d4b0, 0x79104671, 0xec31e35a, 0x4fe0b555, - 0xc7d9cbad, 0xad057f55, 0xe94cc759, 0x7fe0b043, 0xe4529f2e, 0x0d4dd4b2, 0x9f11a54d, 0x031e2e4e, - 0xe6014f5f, 0x11d1ca6c, 0x26bd7f61, 0xeb86854f, 0x4d347b57, 0x116bbe2e, 0xdba7234e, 0x7bcbfd2e, - 0x174dd4b2, 0x6686762e, 0xb089ba50, 0xc6258246, 0x087e767b, 0xc4a8cb4a, 0x595dba50, 0x7f0ae502, - 0x7b1dbd5a, 0xa0603492, 0x57d1af4b, 0x9e21ffd4, 0x6393064d, 0x7407376e, 0xe484762e, 0x122a4e53, - 0x4a37aa43, 0x3888a6be, 0xee77864e, 0x039c8dd5, 0x688d89af, 0x0e988f62, 0x08218246, 0xfc2f8246, - 0xd1d97040, 0xd64cd4b2, 0x5ae4a6b8, 0x7d0de9bc, 0x8d304d61, 0x06c5c672, 0xa4c8bd4d, 0xe0fd373b, - 0x575ebe4d, 0x72d26277, 0x55570f55, 0x77b154d9, 0xe214293a, 0xfc740f4b, 0xfe3f6a57, 0xa9c55f02, - 0xae4054db, 0x2394d918, 0xb511b24a, 0xb8741ab2, 0x0758e65e, 0xc7b5795b, 0xb0a30a4c, 0xaf7f170c, - 0xf3b4762e, 0x8179576d, 0x738a1581, 0x4b95b64c, 0x9829b618, 0x1bea932e, 0x7bdeaa4b, 0xcb5e0281, - 0x65618f54, 0x0658474b, 0x27066acf, 0x40556d65, 0x7d204d53, 0xf28bc244, 0xdce23455, 0xadc0ff54, - 0x3863c948, 0xcee34e5f, 0xdeb85e02, 0x2ed17a61, 0x6a7b094d, 0x7f0cfc40, 0x59603f54, 0x3220afbc, - 0xb5dfd962, 0x125d21c0, 0x13f8d243, 0xacfefb4e, 0x86c2c147, 0x3d8bbd59, 0xbd02a21f, 0x2593042e, - 0xc6a17a7c, 0x28925861, 0xb487ed44, 0xb5f4fd6d, 0x90c28a45, 0x5a14f74d, 0x43d71b4c, 0x728ebb5d, - 0x885bf950, 0x08134dd0, 0x38ec046e, 0xc575684b, 0x50082d2e, 0xa2f47757, 0x270f86ae, 0xf3ff6462, - 0x10ed3f4e, 0x4b58d462, 0xe01ce23e, 0x8c5b092e, 0x63e52f4e, 0x22c1e85d, 0xa908f54e, 0x8591624f, - 0x2c0fb94e, 0xa280ba3c, 0xb6f41b4c, 0x24f9aa47, 0x27201647, 0x3a3ea6dc, 0xa14fc3be, 0x3c34bdd5, - 0x5b8d4f5b, 0xaadeaf4b, 0xc71cab50, 0x15697a4c, 0x9a1a734c, 0x2a037d81, 0x2590bd59, 0x48ec2741, - 0x53489c5b, 0x7f00314b, 0x2170d362, 0xf2e92542, 0x42c10b44, 0x98f0f118, 0x883a3456, 0x099a932e, - 0xea38f7bc, 0x644e9247, 0xbb61b62e, 0x30e0863d, 0x5f51be54, 0x207215c7, 0x5f306c45, 0xaa7f3932, - 0x98da7d45, 0x4e339b59, 0x2e411581, 0xa808f618, 0xad2c0c59, 0x54476741, 0x09e99fd1, 0x5db8f752, - 0xc16df8bd, 0x1dd4b44f, 0x106edf2e, 0x9e15c180, 0x2ad6b56f, 0x633a5332, 0xff33787c, 0x077cb545, - 0x6610be6d, 0x75aad2c4, 0x72fb4d5b, 0xe81e0f59, 0x576f6332, 0x47333373, 0x351ed783, 0x2d90fb50, - 0x8d5e0f6c, 0x5b27a552, 0xdb293ebb, 0xe55ef950, 0x4b133ad8, 0x75df975a, 0x7b6a8740, 0xa899464b, - 0xfab15161, 0x10f8b64d, 0xd055ea4d, 0xee8e146b, 0x4b14afb8, 0x4bc1c44a, 0x9b961dcc, 0xd111ff43, - 0xfca0b745, 0xc800e412, 0x0afad9d1, 0xf751c350, 0xf9f0cccf, 0xa290a545, 0x8ef13763, 0x7ec70d59, - 0x2b066acf, 0x65496c45, 0xade02c1b, 0xae6eb077, 0x92c1e65b, 0xc064e6a9, 0xc649e56d, 0x5287a243, - 0x36de4f5b, 0x5b1df6ad, 0x65c39a59, 0xdba805b2, 0x20067aa8, 0x6457e56d, 0x3cee26cf, 0xfd3ff26d, - 0x04f86d4a, 0x06b8e048, 0xa93bcd5c, 0x91135852, 0xbe90a643, 0x8fa0094d, 0x06d8215f, 0x2677094d, - 0xd735685c, 0x164a00c9, 0x5209ac5f, 0xa9564c5c, 0x3b504f5f, 0xcc826bd0, 0x4615042e, 0x5fe13b4a, - 0x8c81b86d, 0x879ab68c, 0x1de564b8, 0x434487d8, 0x2dcb1b63, 0x82ab524a, 0xb0676abb, 0xa13d9c62, - 0xdbb5b86d, 0x5b7f4b59, 0xaddfb44d, 0xad773532, 0x3997054c, 0x72cebd89, 0xb194544c, 0xc5b8046e, - 0x6e1adeb2, 0xaa5abb51, 0xefb54b44, 0x15efc54f, 0xe9f1bc4d, 0x5f401b6c, 0x97f018ad, 0xc82f9252, - 0x2cdc762e, 0x8e52e56d, 0x1827175e, 0x9b7d7d80, 0xb2ad6845, 0x51065140, 0x71180a18, 0x5b27006c, - 0x0621e255, 0x721cbe58, 0x670c0cb8, 0xf8bd715d, 0xe0bdc5d9, 0xed843501, 0x4b84554d, 0x7f1a18bc, - 0x53bcaf47, 0x5729d35f, 0xf0dda246, 0x22382bd0, 0x4d641fb0, 0x316afcde, 0x50a22f1f, 0x73608046, - 0xc461d84a, 0xb2dbe247, + 0xfc01a8c0, }; void DumpAddresses() @@ -1670,8 +1598,10 @@ void StartNode(void* parg) MapPort(fUseUPnP); // Get addresses from IRC and advertise ours - if (!CreateThread(ThreadIRCSeed, NULL)) - printf("Error: CreateThread(ThreadIRCSeed) failed\n"); + // if (!CreateThread(ThreadIRCSeed, NULL)) + // printf("Error: CreateThread(ThreadIRCSeed) failed\n"); + // IRC disabled with ppcoin + printf("IRC seeding/communication disabled\n"); // Send and receive from sockets, accept connections if (!CreateThread(ThreadSocketHandler, NULL)) diff --git a/src/net.h b/src/net.h index 65118a4..c0cda53 100644 --- a/src/net.h +++ b/src/net.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NET_H @@ -86,6 +87,7 @@ extern bool fClient; extern bool fAllowDNS; extern uint64 nLocalServices; extern CAddress addrLocalHost; +extern CAddress addrSeenByPeer; extern uint64 nLocalHostNonce; extern boost::array vnThreadsRunning; extern CAddrMan addrman; @@ -151,6 +153,7 @@ public: std::set setAddrKnown; bool fGetAddr; std::set setKnown; + uint256 hashCheckpointKnown; // ppcoin: known sent sync-checkpoint // inventory based relay mruset setInventoryKnown; @@ -185,6 +188,7 @@ public: nStartingHeight = -1; fGetAddr = false; nMisbehavior = 0; + hashCheckpointKnown = 0; setInventoryKnown.max_size(SendBufferSize() / 1000); // Be shy and don't send version until we hear diff --git a/src/ppcoin/genesis.cpp b/src/ppcoin/genesis.cpp new file mode 100644 index 0000000..9609161 --- /dev/null +++ b/src/ppcoin/genesis.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2011 The PPCoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. +#include "../headers.h" +#include "../db.h" +#include "../net.h" +#include "../init.h" +#include "../main.h" +#include "../util.h" + +using namespace std; +using namespace boost; + +int main(int argc, char *argv[]) +{ + fPrintToConsole = true; + printf("PPCoin Begin Genesis Block\n"); + + // Genesis block + const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high"; + CTransaction txNew; + txNew.vin.resize(1); + txNew.vout.resize(1); + txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vout[0].SetEmpty(); + CBlock block; + block.vtx.push_back(txNew); + block.hashPrevBlock = 0; + block.hashMerkleRoot = block.BuildMerkleTree(); + block.nVersion = 1; + block.nBits = 0x1d00ffff; + block.nTime = GetAdjustedTime(); + block.nNonce = 0; + + CBigNum bnTarget; + bnTarget.SetCompact(block.nBits); + + while (block.GetHash() > bnTarget.getuint256()) + { + if ((block.nNonce >> 20) << 20 == block.nNonce) + { + if (block.vtx[0].nTime + 7200 < GetAdjustedTime() + 60) + block.vtx[0].nTime = GetAdjustedTime(); + block.nTime = GetAdjustedTime(); + printf("n=%dM hash=%s\n", block.nNonce >> 20, + block.GetHash().ToString().c_str()); + } + block.nNonce++; + } + + printf("PPCoin Found Genesis Block:\n"); + printf("genesis hash=%s\n", block.GetHash().ToString().c_str()); + printf("merkle root=%s\n", block.hashMerkleRoot.ToString().c_str()); + block.print(); + + printf("PPCoin End Genesis Block\n"); +} diff --git a/src/ppcoin/obj/.gitignore b/src/ppcoin/obj/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/src/ppcoin/obj/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/src/protocol.h b/src/protocol.h index b516f1b..30f9714 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -15,10 +15,15 @@ #include #include "uint256.h" +#define PPCOIN_PORT 9901 +#define RPC_PORT 9902 +#define TESTNET_PORT 9903 + extern bool fTestNet; + static inline unsigned short GetDefaultPort(const bool testnet = fTestNet) { - return testnet ? 18333 : 8333; + return testnet ? TESTNET_PORT : PPCOIN_PORT; } diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 9a9a489..1356a16 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -34,9 +34,9 @@ QString BitcoinUnits::name(int unit) { switch(unit) { - case BTC: return QString("BTC"); - case mBTC: return QString("mBTC"); - case uBTC: return QString::fromUtf8("μBTC"); + case BTC: return QString("PPC"); + case mBTC: return QString("mPPC"); + case uBTC: return QString::fromUtf8("μPPC"); default: return QString("???"); } } @@ -45,9 +45,9 @@ QString BitcoinUnits::description(int unit) { switch(unit) { - case BTC: return QString("Bitcoins"); - case mBTC: return QString("Milli-Bitcoins (1 / 1,000)"); - case uBTC: return QString("Micro-Bitcoins (1 / 1,000,000)"); + case BTC: return QString("PPCoins"); + case mBTC: return QString("Milli-PPCoins (1 / 1,000)"); + case uBTC: return QString("Micro-PPCoins (1 / 1,000,000)"); default: return QString("???"); } } @@ -56,10 +56,10 @@ qint64 BitcoinUnits::factor(int unit) { switch(unit) { - case BTC: return 100000000; - case mBTC: return 100000; - case uBTC: return 100; - default: return 100000000; + case BTC: return 1000000; + case mBTC: return 1000; + case uBTC: return 1; + default: return 1000000; } } @@ -67,9 +67,9 @@ int BitcoinUnits::amountDigits(int unit) { switch(unit) { - case BTC: return 8; // 21,000,000 (# digits, without commas) - case mBTC: return 11; // 21,000,000,000 - case uBTC: return 14; // 21,000,000,000,000 + case BTC: return 10; // 21,000,000 (# digits, without commas) + case mBTC: return 13; // 21,000,000,000 + case uBTC: return 16; // 21,000,000,000,000 default: return 0; } } @@ -78,9 +78,9 @@ int BitcoinUnits::decimals(int unit) { switch(unit) { - case BTC: return 8; - case mBTC: return 5; - case uBTC: return 2; + case BTC: return 6; + case mBTC: return 3; + case uBTC: return 0; default: return 0; } } diff --git a/src/script.cpp b/src/script.cpp index ccb19c3..a3ab8e1 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include @@ -19,7 +20,6 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc -typedef vector valtype; static const valtype vchFalse(0); static const valtype vchZero(0); static const valtype vchTrue(1, 1); @@ -1584,6 +1584,7 @@ bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTrans assert(nIn < txTo.vin.size()); CTxIn& txin = txTo.vin[nIn]; assert(txin.prevout.n < txFrom.vout.size()); + assert(txin.prevout.hash == txFrom.GetHash()); const CTxOut& txout = txFrom.vout[txin.prevout.n]; // Leave out the signature from the hash, since a signature can't sign itself. diff --git a/src/script.h b/src/script.h index 5397a19..2aa08fe 100644 --- a/src/script.h +++ b/src/script.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef H_BITCOIN_SCRIPT @@ -12,6 +13,8 @@ #include +typedef std::vector valtype; + class CTransaction; class CKeyStore; diff --git a/src/serialize.h b/src/serialize.h index 349a40b..ca12051 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SERIALIZE_H @@ -155,8 +156,14 @@ template inline void Unserialize(Stream& s, bool& a, int, int=0 - - +#ifndef THROW_WITH_STACKTRACE +#define THROW_WITH_STACKTRACE(exception) \ +{ \ + LogStackTrace(); \ + throw (exception); \ +} +void LogStackTrace(); +#endif // // Compact size @@ -234,7 +241,7 @@ uint64 ReadCompactSize(Stream& is) nSizeRet = xSize; } if (nSizeRet > (uint64)MAX_SIZE) - throw std::ios_base::failure("ReadCompactSize() : size too large"); + THROW_WITH_STACKTRACE(std::ios_base::failure("ReadCompactSize() : size too large")); return nSizeRet; } @@ -905,7 +912,7 @@ public: { state |= bits; if (state & exceptmask) - throw std::ios_base::failure(psz); + THROW_WITH_STACKTRACE(std::ios_base::failure(psz)); } bool eof() const { return size() == 0; } @@ -1120,7 +1127,7 @@ public: { state |= bits; if (state & exceptmask) - throw std::ios_base::failure(psz); + THROW_WITH_STACKTRACE(std::ios_base::failure(psz)); } bool fail() const { return state & (std::ios::badbit | std::ios::failbit); } diff --git a/src/util.cpp b/src/util.cpp index a407714..89509b1 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -53,6 +54,10 @@ namespace boost { #include "shlwapi.h" #endif +#ifndef WIN32 +#include +#endif + using namespace std; using namespace boost; @@ -189,7 +194,7 @@ uint256 GetRandHash() - +static FILE* fileout = NULL; inline int OutputDebugStringF(const char* pszFormat, ...) { @@ -205,8 +210,6 @@ inline int OutputDebugStringF(const char* pszFormat, ...) else { // print to debug.log - static FILE* fileout = NULL; - if (!fileout) { boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; @@ -371,7 +374,7 @@ string FormatMoney(int64 n, bool fPlus) int64 n_abs = (n > 0 ? n : -n); int64 quotient = n_abs/COIN; int64 remainder = n_abs%COIN; - string str = strprintf("%"PRI64d".%08"PRI64d, quotient, remainder); + string str = strprintf("%"PRI64d".%06"PRI64d, quotient, remainder); // Right-trim excess 0's before the decimal point: int nTrim = 0; @@ -801,6 +804,19 @@ void PrintException(std::exception* pex, const char* pszThread) throw; } +void LogStackTrace() { + printf("\n\n******* exception encountered *******\n"); + if (fileout) + { +#ifndef WIN32 + void* pszBuffer[32]; + size_t size; + size = backtrace(pszBuffer, 32); + backtrace_symbols_fd(pszBuffer, size, fileno(fileout)); +#endif + } +} + void PrintExceptionContinue(std::exception* pex, const char* pszThread) { char pszMessage[10000]; @@ -836,12 +852,12 @@ boost::filesystem::path GetDefaultDataDir() { namespace fs = boost::filesystem; - // Windows: C:\Documents and Settings\username\Application Data\Bitcoin - // Mac: ~/Library/Application Support/Bitcoin - // Unix: ~/.bitcoin + // Windows: C:\Documents and Settings\username\Application Data\PPCoin + // Mac: ~/Library/Application Support/PPCoin + // Unix: ~/.ppcoin #ifdef WIN32 // Windows - return MyGetSpecialFolderPath(CSIDL_APPDATA, true) / "Bitcoin"; + return MyGetSpecialFolderPath(CSIDL_APPDATA, true) / "PPCoin"; #else fs::path pathRet; char* pszHome = getenv("HOME"); @@ -853,10 +869,10 @@ boost::filesystem::path GetDefaultDataDir() // Mac pathRet /= "Library/Application Support"; fs::create_directory(pathRet); - return pathRet / "Bitcoin"; + return pathRet / "PPCoin"; #else // Unix - return pathRet / ".bitcoin"; + return pathRet / ".ppcoin"; #endif #endif } @@ -900,7 +916,7 @@ boost::filesystem::path GetConfigFile() { namespace fs = boost::filesystem; - fs::path pathConfigFile(GetArg("-conf", "bitcoin.conf")); + fs::path pathConfigFile(GetArg("-conf", "ppcoin.conf")); if (!pathConfigFile.is_complete()) pathConfigFile = GetDataDir(false) / pathConfigFile; return pathConfigFile; } @@ -936,7 +952,7 @@ boost::filesystem::path GetPidFile() { namespace fs = boost::filesystem; - fs::path pathPidFile(GetArg("-pid", "bitcoind.pid")); + fs::path pathPidFile(GetArg("-pid", "ppcoind.pid")); if (!pathPidFile.is_complete()) pathPidFile = GetDataDir() / pathPidFile; return pathPidFile; } @@ -1203,6 +1219,7 @@ bool GetStartOnSystemStartup() if (line.find("Hidden") != string::npos && line.find("true") != string::npos) return false; +>>>>>>> bitcoin } optionFile.close(); diff --git a/src/util.h b/src/util.h index ef15260..dc40af5 100644 --- a/src/util.h +++ b/src/util.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UTIL_H @@ -36,8 +37,8 @@ typedef int pid_t; /* define for windows compatiblity */ typedef long long int64; typedef unsigned long long uint64; -static const int64 COIN = 100000000; -static const int64 CENT = 1000000; +static const int64 COIN = 1000000; +static const int64 CENT = 10000; #define loop for (;;) #define BEGIN(a) ((char*)&(a)) @@ -103,8 +104,14 @@ inline void Sleep(int64 n) } #endif - - +#ifndef THROW_WITH_STACKTRACE +#define THROW_WITH_STACKTRACE(exception) \ +{ \ + LogStackTrace(); \ + throw (exception); \ +} +void LogStackTrace(); +#endif @@ -661,9 +668,9 @@ public: // Note: It turns out we might have been able to use boost::thread // by using TerminateThread(boost::thread.native_handle(), 0); #ifdef WIN32 -typedef HANDLE pthread_t; +typedef HANDLE bitcoin_pthread_t; -inline pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false) +inline bitcoin_pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false) { DWORD nUnused = 0; HANDLE hthread = @@ -677,12 +684,12 @@ inline pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=fa if (hthread == NULL) { printf("Error: CreateThread() returned %d\n", GetLastError()); - return (pthread_t)0; + return (bitcoin_pthread_t)0; } if (!fWantHandle) { CloseHandle(hthread); - return (pthread_t)-1; + return (bitcoin_pthread_t)-1; } return hthread; } diff --git a/src/version.cpp b/src/version.cpp index 60b7aae..3ae1784 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -49,9 +49,9 @@ const std::string CLIENT_NAME("Satoshi"); #ifndef BUILD_DESC # ifdef GIT_COMMIT_ID -# define BUILD_DESC BUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, GIT_COMMIT_ID) +# define BUILD_DESC BUILD_DESC_FROM_COMMIT(PPCOIN_VERSION_MAJOR, PPCOIN_VERSION_MINOR, PPCOIN_VERSION_REVISION, PPCOIN_VERSION_BUILD, GIT_COMMIT_ID) # else -# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD) +# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(PPCOIN_VERSION_MAJOR, PPCOIN_VERSION_MINOR, PPCOIN_VERSION_REVISION, PPCOIN_VERSION_BUILD) # endif #endif diff --git a/src/version.h b/src/version.h index 9718e75..29efa06 100644 --- a/src/version.h +++ b/src/version.h @@ -26,6 +26,12 @@ extern const std::string CLIENT_NAME; extern const std::string CLIENT_BUILD; extern const std::string CLIENT_DATE; +// ppcoin version - intended for display purpose ONLY +#define PPCOIN_VERSION_MAJOR 0 +#define PPCOIN_VERSION_MINOR 1 +#define PPCOIN_VERSION_REVISION 0 +#define PPCOIN_VERSION_BUILD 0 + // // network protocol versioning // @@ -33,7 +39,7 @@ extern const std::string CLIENT_DATE; static const int PROTOCOL_VERSION = 60001; // earlier versions not supported as of Feb 2012, and are disconnected -static const int MIN_PROTO_VERSION = 209; +static const int MIN_PROTO_VERSION = 60001; // nTime field added to CAddress, starting with this version; // if possible, avoid requesting addresses nodes older than this diff --git a/src/wallet.cpp b/src/wallet.cpp index d7a70fe..8acc825 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,11 +1,13 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "wallet.h" #include "walletdb.h" #include "crypter.h" +#include "checkpoints.h" #include "ui_interface.h" using namespace std; @@ -69,6 +71,10 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } +// ppcoin: optional setting to create coinstake only when unlocked; +// serves to disable the trivial sendmoney when OS account compromised +bool fWalletUnlockStakeOnly = false; + bool CWallet::Unlock(const SecureString& strWalletPassphrase) { if (!IsLocked()) @@ -294,7 +300,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) CWalletTx& wtx = (*mi).second; if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) { - printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + printf("WalletUpdateSpent found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); vWalletUpdated.push_back(txin.prevout.hash); @@ -485,7 +491,7 @@ int CWalletTx::GetRequestCount() const int nRequests = -1; { LOCK(pwallet->cs_wallet); - if (IsCoinBase()) + if (IsCoinBase() || IsCoinStake()) { // Generated block if (hashBlock != 0) @@ -526,7 +532,7 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l listSent.clear(); strSentAccount = strFromAccount; - if (IsCoinBase()) + if (IsCoinBase() || IsCoinStake()) { if (GetBlocksToMaturity() > 0) nGeneratedImmature = pwallet->GetCredit(*this); @@ -717,7 +723,7 @@ void CWallet::ReacceptWalletTransactions() BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { CWalletTx& wtx = item.second; - if (wtx.IsCoinBase() && wtx.IsSpent(0)) + if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1))) continue; CTxIndex txindex; @@ -743,7 +749,7 @@ void CWallet::ReacceptWalletTransactions() } if (fUpdated) { - printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + printf("ReacceptWalletTransactions found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkDirty(); wtx.WriteToDisk(); } @@ -751,7 +757,7 @@ void CWallet::ReacceptWalletTransactions() else { // Reaccept any txes of ours that aren't already in a block - if (!wtx.IsCoinBase()) + if (!(wtx.IsCoinBase() || wtx.IsCoinStake())) wtx.AcceptWalletTransaction(txdb, false); } } @@ -768,14 +774,14 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb) { BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) { - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) { uint256 hash = tx.GetHash(); if (!txdb.ContainsTx(hash)) RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); } } - if (!IsCoinBase()) + if (!(IsCoinBase() || IsCoinStake())) { uint256 hash = GetHash(); if (!txdb.ContainsTx(hash)) @@ -877,7 +883,35 @@ int64 CWallet::GetUnconfirmedBalance() const return nTotal; } -bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) const +// ppcoin: total coins staked (non-spendable until maturity) +int64 CWallet::GetStake() const +{ + int64 nTotal = 0; + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) + nTotal += CWallet::GetCredit(*pcoin); + } + return nTotal; +} + +int64 CWallet::GetNewMint() const +{ + int64 nTotal = 0; + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) + nTotal += CWallet::GetCredit(*pcoin); + } + return nTotal; +} + + +bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) const { setCoinsRet.clear(); nValueRet = 0; @@ -902,7 +936,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) continue; - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + if ((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetBlocksToMaturity() > 0) continue; int nDepth = pcoin->GetDepthInMainChain(); @@ -914,6 +948,9 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe if (pcoin->IsSpent(i) || !IsMine(pcoin->vout[i])) continue; + if (pcoin->nTime > nSpendTime) + continue; // ppcoin: timestamp must not exceed spend time + int64 n = pcoin->vout[i].nValue; if (n <= 0) @@ -1022,11 +1059,11 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe return true; } -bool CWallet::SelectCoins(int64 nTargetValue, set >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet) const { - return (SelectCoinsMinConf(nTargetValue, 1, 6, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 1, 1, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 0, 1, setCoinsRet, nValueRet)); + return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, setCoinsRet, nValueRet)); } @@ -1067,7 +1104,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Choose coins to use set > setCoins; int64 nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn)) + if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn)) return false; BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { @@ -1130,8 +1167,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); - bool fAllowFree = CTransaction::AllowFree(dPriority); - int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND); + int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -1156,6 +1192,94 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& w return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet); } +// ppcoin: create coin stake transaction +bool CWallet::CreateCoinStake(unsigned int nBits, CTransaction& txNew) +{ + CBigNum bnTargetPerCoinDay; + bnTargetPerCoinDay.SetCompact(nBits); + + LOCK2(cs_main, cs_wallet); + txNew.vin.clear(); + txNew.vout.clear(); + // Mark coin stake transaction + CScript scriptEmpty; + scriptEmpty.clear(); + txNew.vout.push_back(CTxOut(0, scriptEmpty)); + // Choose coins to use + int64 nBalance = GetBalance(); + if (nBalance <= nBalanceReserve) + return false; + set > setCoins; + vector vwtxPrev; + int64 nValueIn = 0; + if (!SelectCoins(nBalance - nBalanceReserve, txNew.nTime, setCoins, nValueIn)) + return false; + if (setCoins.empty()) + return false; + int64 nCredit = 0; + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + CTxDB txdb("r"); + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex)) + continue; + + // Read block header + CBlock block; + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + continue; + if (block.GetBlockTime() + STAKE_MIN_AGE > txNew.nTime) + continue; // only count coins meeting min age requirement + + int64 nValueIn = pcoin.first->vout[pcoin.second].nValue; + CBigNum bnCoinDay = CBigNum(nValueIn) * (txNew.nTime-pcoin.first->nTime) / COIN / (24 * 60 * 60); + // Calculate hash + CDataStream ss(SER_GETHASH, 0); + ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << pcoin.first->nTime << pcoin.second << txNew.nTime; + if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay) + { + txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + nCredit += pcoin.first->vout[pcoin.second].nValue; + vwtxPrev.push_back(pcoin.first); + // Set output scriptPubKey + txNew.vout.push_back(CTxOut(0, pcoin.first->vout[pcoin.second].scriptPubKey)); + break; + } + } + if (nCredit == 0 || nCredit > nBalance - nBalanceReserve) + return false; + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + if (pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey && pcoin.first->GetHash() != txNew.vin[0].prevout.hash) + { + if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nBalanceReserve) + break; + txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + nCredit += pcoin.first->vout[pcoin.second].nValue; + vwtxPrev.push_back(pcoin.first); + } + } + // Calculate coin age reward + { + uint64 nCoinAge; + CTxDB txdb("r"); + if (!txNew.GetCoinAge(txdb, nCoinAge)) + return error("CreateCoinStake : failed to calculate coin age"); + nCredit += GetProofOfStakeReward(nCoinAge); + } + // Set output amount + txNew.vout[1].nValue = nCredit; + + // Sign + int nIn = 0; + BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev) + { + if (!SignSignature(*this, *pcoin, txNew, nIn++)) + return error("CreateCoinStake : failed to sign coinstake"); + } + return true; +} + // Call after CreateTransaction unless you want to abort bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { @@ -1220,6 +1344,12 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, printf("SendMoney() : %s", strError.c_str()); return strError; } + if (fWalletUnlockStakeOnly) + { + string strError = _("Error: Wallet unlocked for coinstake only, unable to create transaction."); + printf("SendMoney() : %s", strError.c_str()); + return strError; + } if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) { string strError; @@ -1311,10 +1441,15 @@ void CWallet::PrintWallet(const CBlock& block) { { LOCK(cs_wallet); - if (mapWallet.count(block.vtx[0].GetHash())) + if (block.IsProofOfWork() && mapWallet.count(block.vtx[0].GetHash())) { CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; - printf(" mine: %d %d %d", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); + printf(" mine: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); + } + if (block.IsProofOfStake() && mapWallet.count(block.vtx[1].GetHash())) + { + CWalletTx& wtx = mapWallet[block.vtx[1].GetHash()]; + printf(" stake: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); } } printf("\n"); @@ -1505,6 +1640,107 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } +// ppcoin: check 'spent' consistency between wallet and txindex +bool CWallet::CheckSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion) +{ + nMismatchFound = 0; + nBalanceInQuestion = 0; + + LOCK(cs_wallet); + vector vCoins; + vCoins.reserve(mapWallet.size()); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + vCoins.push_back(&(*it).second); + + CTxDB txdb("r"); + BOOST_FOREACH(const CWalletTx* pcoin, vCoins) + { + // Find the corresponding transaction index + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex)) + continue; + for (int n=0; n < pcoin->vout.size(); n++) + { + if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull())) + { + printf("CheckSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + } + else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull())) + { + printf("CheckSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + } + } + } + return (nMismatchFound == 0); +} + +// ppcoin: fix wallet spent state according to txindex +void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion) +{ + nMismatchFound = 0; + nBalanceInQuestion = 0; + + LOCK(cs_wallet); + vector vCoins; + vCoins.reserve(mapWallet.size()); + for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + vCoins.push_back(&(*it).second); + + CTxDB txdb("r"); + BOOST_FOREACH(CWalletTx* pcoin, vCoins) + { + // Find the corresponding transaction index + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex)) + continue; + for (int n=0; n < pcoin->vout.size(); n++) + { + if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull())) + { + printf("FixSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + pcoin->MarkUnspent(n); + pcoin->WriteToDisk(); + } + else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull())) + { + printf("FixSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + pcoin->MarkSpent(n); + pcoin->WriteToDisk(); + } + } + } +} + +// ppcoin: disable transaction (only for coinstake) +void CWallet::DisableTransaction(const CTransaction &tx) +{ + if (!tx.IsCoinStake() || !IsFromMe(tx)) + return; // only disconnecting coinstake requires marking input unspent + + LOCK(cs_wallet); + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + map::iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) + { + CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.vout.size() && IsMine(prev.vout[txin.prevout.n])) + { + prev.MarkUnspent(txin.prevout.n); + prev.WriteToDisk(); + } + } + } +} + vector CReserveKey::GetReservedKey() { if (nIndex == -1) diff --git a/src/wallet.h b/src/wallet.h index 9e451f8..d3c25f9 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_H @@ -10,6 +11,8 @@ #include "keystore.h" #include "script.h" +extern bool fWalletUnlockStakeOnly; + class CWalletTx; class CReserveKey; class CWalletDB; @@ -59,8 +62,8 @@ public: class CWallet : public CCryptoKeyStore { private: - bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, std::set >& setCoinsRet, int64& nValueRet) const; - bool SelectCoins(int64 nTargetValue, std::set >& setCoinsRet, int64& nValueRet) const; + bool SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, std::set >& setCoinsRet, int64& nValueRet) const; + bool SelectCoins(int64 nTargetValue, unsigned int nSpendTime, std::set >& setCoinsRet, int64& nValueRet) const; CWalletDB *pwalletdbEncryption; @@ -145,8 +148,11 @@ public: void ResendWalletTransactions(); int64 GetBalance() const; int64 GetUnconfirmedBalance() const; + int64 GetStake() const; + int64 GetNewMint() const; bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); + bool CreateCoinStake(unsigned int nBits, CTransaction& txNew); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); std::string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); std::string SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); @@ -269,6 +275,10 @@ public: // get the current wallet format (the oldest client version guaranteed to understand this wallet) int GetVersion() { return nWalletVersion; } + + bool CheckSpentCoins(int& nMismatchSpent, int64& nBalanceInQuestion); + void FixSpentCoins(int& nMismatchSpent, int64& nBalanceInQuestion); + void DisableTransaction(const CTransaction &tx); }; /** A key allocated from the key pool. */ @@ -459,6 +469,18 @@ public: } } + void MarkUnspent(unsigned int nOut) + { + if (nOut >= vout.size()) + throw std::runtime_error("CWalletTx::MarkUnspent() : nOut out of range"); + vfSpent.resize(vout.size()); + if (vfSpent[nOut]) + { + vfSpent[nOut] = false; + fAvailableCreditCached = false; + } + } + bool IsSpent(unsigned int nOut) const { if (nOut >= vout.size()) @@ -482,7 +504,7 @@ public: int64 GetCredit(bool fUseCache=true) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) + if ((IsCoinBase() || IsCoinStake()) && GetBlocksToMaturity() > 0) return 0; // GetBalance can assume transactions in mapWallet won't change @@ -496,7 +518,7 @@ public: int64 GetAvailableCredit(bool fUseCache=true) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) + if ((IsCoinBase() || IsCoinStake()) && GetBlocksToMaturity() > 0) return 0; if (fUseCache && fAvailableCreditCached)