diff --git a/.travis.yml b/.travis.yml index e0f7ac3..9439f01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ install: # hashes - pip install tribus-hash - pip install blake256 + - pip install scrypt - pip install x11_hash - pip install xevan_hash - pip install quark_hash diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 3f655aa..1a45bca 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1174,6 +1174,31 @@ class Reddcoin(Coin): RPC_PORT = 45443 +class TokenPay(ScryptMixin, Coin): + NAME = "TokenPay" + SHORTNAME = "TPAY" + NET = "mainnet" + P2PKH_VERBYTE = bytes.fromhex("41") + P2SH_VERBYTES = [bytes.fromhex("7e")] + WIF_BYTE = bytes.fromhex("b3") + GENESIS_HASH = ('000008b71ab32e585a23f0de642dc113' + '740144e94c0ece047751e9781f953ae9') + DESERIALIZER = lib_tx.DeserializerTokenPay + DAEMON = daemon.LegacyRPCDaemon + TX_COUNT = 147934 + TX_COUNT_HEIGHT = 73967 + TX_PER_BLOCK = 100 + RPC_PORT = 8800 + REORG_LIMIT = 500 + XPUB_VERBYTES = bytes.fromhex("0488B21E") + XPRV_VERBYTES = bytes.fromhex("0488ADE4") + + PEERS = [ + "electrum-us.tpay.ai s", + "electrum-eu.tpay.ai s", + ] + + class Vertcoin(Coin): NAME = "Vertcoin" SHORTNAME = "VTC" diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index 4aa80e2..a1391c1 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -30,6 +30,7 @@ from collections import namedtuple from electrumx.lib.hash import sha256, double_sha256, hash_to_hex_str +from electrumx.lib.script import OpCodes from electrumx.lib.util import ( unpack_le_int32_from, unpack_le_int64_from, unpack_le_uint16_from, unpack_le_uint32_from, unpack_le_uint64_from, pack_le_int32, pack_varint, @@ -422,6 +423,74 @@ class DeserializerGroestlcoin(DeserializerSegWit): TX_HASH_FN = staticmethod(sha256) +class TxInputTokenPay(TxInput): + '''Class representing a TokenPay transaction input.''' + + OP_ANON_MARKER = 0xb9 + # 2byte marker (cpubkey + sigc + sigr) + MIN_ANON_IN_SIZE = 2 + (33 + 32 + 32) + + def _is_anon_input(self): + return (len(self.script) >= self.MIN_ANON_IN_SIZE and + self.script[0] == OpCodes.OP_RETURN and + self.script[1] == self.OP_ANON_MARKER) + + def is_generation(self): + # Transactions comming in from stealth addresses are seen by + # the blockchain as newly minted coins. The reverse, where coins + # are sent TO a stealth address, are seen by the blockchain as + # a coin burn. + if self._is_anon_input(): + return True + return super(TxInputTokenPay, self).is_generation() + + +class TxInputTokenPayStealth( + namedtuple("TxInput", "keyimage ringsize script sequence")): + '''Class representing a TokenPay stealth transaction input.''' + + def __str__(self): + script = self.script.hex() + keyimage = bytes(self.keyimage).hex() + return ("Input({}, {:d}, script={}, sequence={:d})" + .format(keyimage, self.ringsize[1], script, self.sequence)) + + def is_generation(self): + return True + + def serialize(self): + return b''.join(( + self.keyimage, + self.ringsize, + pack_varbytes(self.script), + pack_le_uint32(self.sequence), + )) + + +class DeserializerTokenPay(DeserializerTxTime): + + def _read_input(self): + txin = TxInputTokenPay( + self._read_nbytes(32), # prev_hash + self._read_le_uint32(), # prev_idx + self._read_varbytes(), # script + self._read_le_uint32(), # sequence + ) + if txin._is_anon_input(): + # Not sure if this is actually needed, and seems + # extra work for no immediate benefit, but it at + # least correctly represents a stealth input + raw = txin.serialize() + deserializer = Deserializer(raw) + txin = TxInputTokenPayStealth( + deserializer._read_nbytes(33), # keyimage + deserializer._read_nbytes(3), # ringsize + deserializer._read_varbytes(), # script + deserializer._read_le_uint32() # sequence + ) + return txin + + # Decred class TxInputDcr(namedtuple("TxInput", "prev_hash prev_idx tree sequence")): '''Class representing a Decred transaction input.''' diff --git a/tests/blocks/tokenpay_mainnet_20000.json b/tests/blocks/tokenpay_mainnet_20000.json new file mode 100644 index 0000000..e43eeb7 --- /dev/null +++ b/tests/blocks/tokenpay_mainnet_20000.json @@ -0,0 +1,28 @@ +{ + "hash" : "a04c982af88368f934aada4c04a2907c204548b2f23b123104e5d5681318c58e", + "confirmations" : 223761, + "size" : 400, + "height" : 20000, + "version" : 6, + "merkleroot" : "9a15ca74bc6caf91be0517be05236a2c013993bbd9c0b28fd61dacbe66ff0edb", + "mint" : 0.06584667, + "time" : 1517342128, + "nonce" : 0, + "bits" : "1e00857c", + "difficulty" : 0.00749140, + "blocktrust" : "1eaf6bd", + "chaintrust" : "376cd8e0ac", + "previousblockhash" : "03e5e53d9edacc59720e22800f9f0230b7100abd1c2f388c64a2e0bd98df2710", + "nextblockhash" : "caff0ac56f5b367dc4f4da3d2b4a8848d4116079c353234ab2e0fe6f3f69ff82", + "flags" : "proof-of-stake", + "proofhash" : "000019d0fa709575179a07d46604f5d4efbf7bb02b5a1308b6a9f4410ba3328e", + "entropybit" : 0, + "modifier" : "1c5a2302bda4c0f3", + "modifierv2" : "5740fb0cc8bf2853ad258f16c363bbb92387cf85ac874ec553917c903ea70444", + "tx" : [ + "d2e1100088af3e19976cac83f167ceffe725375442907d739341a40896d1089c", + "c24b1208937b6f634c0410cbc9ecf00a36837451ae27a85550b6af2a35b36f1f" + ], + "signature" : "304402206d29ae84b3d1e6ac4cafefee9aaf8b96a5b2c1d60fa43f8805916db72e47e90502203aed70e4cf36ecd5f07d01087e8c7bc9b232825204116c6e2f77875ce0480f68", + "block": "060000001027df98bde0a2648c382f1cbd0a10b730029f0f80220e7259ccda9e3de5e503db0eff66beac1dd68fb2c0d9bb9339012c6a2305be1705be91af6cbc74ca159ab0cd705a7c85001e000000000201000000b0cd705a010000000000000000000000000000000000000000000000000000000000000000ffffffff0302204effffffff010000000000000000000000000001000000b0cd705a01ca1eea11a6379fbf3c61f18df79065e48aacd60c76c6694490cdf4ecae7c5edf01000000494830450221009bac3e0c4f6432abf7becca230eb69c5781d23a3d82a6c79457edd29fd0eb493022058de72c9ec4d51518f2d6538549aba819d9137e593beaad04aa9085a3043bf4901ffffffff020000000000000000002b0d0da529000000232103e9f972cb71e89a91c02e1512f36c7c92e4ed5f84fdbad3ac77e444e0a2f97cc4ac0000000046304402206d29ae84b3d1e6ac4cafefee9aaf8b96a5b2c1d60fa43f8805916db72e47e90502203aed70e4cf36ecd5f07d01087e8c7bc9b232825204116c6e2f77875ce0480f68" +} \ No newline at end of file diff --git a/tests/lib/test_addresses.py b/tests/lib/test_addresses.py index e8fe710..173f7f2 100644 --- a/tests/lib/test_addresses.py +++ b/tests/lib/test_addresses.py @@ -64,7 +64,9 @@ addresses = [ (coins.PivxTestnet, "yJ8iHtUxj9U4vsXLCZTbPNbuxG6NJNCvb8", "e808105b7bfcc8b102cafa7242089b22c77a3b94", "31d61c3076fa0b2b7c74ef"), (coins.PivxTestnet, "yCcNWqqMhDmsPzKchCPK1ux4HpxK7j3xpB", - "ab72728952c06dfc0f6cf21449dd645422731ec4", "eb3a3155215538d51de7cc") + "ab72728952c06dfc0f6cf21449dd645422731ec4", "eb3a3155215538d51de7cc"), + (coins.TokenPay, "TDE2X28FGtckatxuP3d8s3V726G4TLNHpT", + "23b5dd9b7b402388c7a40bc88c261f3178acf30d", "7c7bdf0e0713f3752f4b88"), ] diff --git a/tests/server/test_env.py b/tests/server/test_env.py index bc92f88..3dcec3c 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -134,7 +134,10 @@ def test_COIN_NET(): e = Env() assert e.coin == lib_coins.PivxTestnet os.environ.pop('NET') - + os.environ['NET'] = 'mainnet' + os.environ['COIN'] = ' TokenPay ' + e = Env() + assert e.coin == lib_coins.TokenPay def test_CACHE_MB(): assert_integer('CACHE_MB', 'cache_MB', 1200)