diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 28c1814998..333d64654c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,9 +2,9 @@ This issue tracker is only for technical issues related to bitcoin-core. -General bitcoin questions and/or support requests and are best directed to the [Bitcoin StackExchange](https://bitcoin.stackexchange.com). +General ravencoin questions and/or support requests and are best directed to the [Ravencoin Discord](https://discord.gg/GwtXdyc). -For reporting security issues, please read instructions at [https://bitcoincore.org/en/contact/](https://bitcoincore.org/en/contact/). +For reporting security issues, please direct message one of the core developers in discord. ### Describe the issue diff --git a/.travis.yml b/.travis.yml index 8907d02239..df437e7c32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,6 @@ env: - CCACHE_COMPRESS=1 - BASE_OUTDIR=$TRAVIS_BUILD_DIR/out - SDK_URL=https://ravencoin.org/depends-sources/sdks - - PYTHON_DEBUG=1 - WINEDEBUG=fixme-all matrix: # ARM @@ -75,7 +74,7 @@ script: - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib - if [ "$RUN_TESTS" = "true" ]; then travis_wait 30 make $MAKEJOBS check VERBOSE=1; fi - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude pruning,dbcrash"; fi - - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage --quiet ${extended}; fi + - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet ${extended}; fi after_script: - echo $TRAVIS_COMMIT_RANGE - echo $TRAVIS_COMMIT_LOG diff --git a/README.md b/README.md index eecf621333..bfa5870e58 100644 --- a/README.md +++ b/README.md @@ -101,5 +101,5 @@ Bitcoin is and always should be focused on its goals of being a better form of m In the new global economy, borders and jurisdictions will be less relevant as more assets are tradable and trade across borders is increasingly frictionless. In an age where people can move significant amounts of wealth instantly using Bitcoin, global consumers will likely demand the same efficiency for their securities and similar asset holdings. -For such a global system to work it will need to be independent of regulatory jurisdictions. This is not due to ideological belief but practicality: if the rails for blockchain asset transfer are not censorship resistance and jurisdiction agnostic, any given jurisdiction may be in conflict with another. In legacy systems, wealth was generally confined in the jurisdiction of the holder and therefor easy to control based on the policies of that jurisdiction. Because of the global nature of blockchain technology any protocol level ability to control wealth would potentially place jurisdictions in conflict and will not be able to operate fairly. +For such a global system to work it will need to be independent of regulatory jurisdictions. This is not due to ideological belief but practicality: if the rails for blockchain asset transfer are not censorship resistance and jurisdiction agnostic, any given jurisdiction may be in conflict with another. In legacy systems, wealth was generally confined in the jurisdiction of the holder and therefore easy to control based on the policies of that jurisdiction. Because of the global nature of blockchain technology any protocol level ability to control wealth would potentially place jurisdictions in conflict and will not be able to operate fairly. diff --git a/assets/tools/README.md b/assets/tools/README.md index 9e620a6c9b..5e52b0fa3d 100644 --- a/assets/tools/README.md +++ b/assets/tools/README.md @@ -11,3 +11,14 @@ Issue assets from a .csv file. Check for assets that have signed documents. * Set the constants at the top of signed_promises.py * ```python signed_promises.py``` + +### Block Facts +Loops through blocks and prints out block information. +* Set the constants at the top of blockfacts.py +* ```python blockfacts.py``` + +### Transaction Facts +Loops through blocks and transactions and prints out tx information. +* Uncomment out print lines to print out more facts +* Set the constants at the top of txfacts.py +* ```python txfacts.py``` diff --git a/assets/tools/asset_audit.py b/assets/tools/asset_audit.py new file mode 100644 index 0000000000..8836043598 --- /dev/null +++ b/assets/tools/asset_audit.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# Script to audit the assets +# Reads the asset (amount has all issuances) +# Reads the balances in every address for the asset. +# Compares the two numbers to checks that qty of all assets are accounted for + +import subprocess +import json + + +#Set this to your raven-cli program +cli = "raven-cli" + +mode = "-testnet" +rpc_port = 18766 +#mode = "-regtest" +#rpc_port = 18443 + +#Set this information in your raven.conf file (in datadir, not testnet3) +rpc_user = 'rpcuser' +rpc_pass = 'rpcpass555' + +def listassets(filter): + rpc_connection = get_rpc_connection() + result = rpc_connection.listassets(filter, True) + return(result) + +def listaddressesbyasset(asset): + rpc_connection = get_rpc_connection() + result = rpc_connection.listaddressesbyasset(asset) + return(result) + +def rpc_call(params): + process = subprocess.Popen([cli, mode, params], stdout=subprocess.PIPE) + out, err = process.communicate() + return(out) + +def generate_blocks(n): + rpc_connection = get_rpc_connection() + hashes = rpc_connection.generate(n) + return(hashes) + +def get_rpc_connection(): + from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + connection = "http://%s:%s@127.0.0.1:%s"%(rpc_user, rpc_pass, rpc_port) + #print("Connection: " + connection) + rpc_connection = AuthServiceProxy(connection) + return(rpc_connection) + +def audit(filter): + assets = listassets(filter) + print("Auditing: " + filter) + #print(assets) + print("Asset count: " + str(len(assets))) + count = 0 + max_dist_asset_name = "" + max_dist_address_count = 0 + for asset, properties in assets.items(): + count=count+1 + total_issued = 0 + total_for_asset = 0 + + print("Auditing asset (" + str(count) + "): " + asset) + for key, value in properties.items(): + if (key == 'amount'): + total_issued += value + print("Total issued for " + asset + " is: " + str(value)) + address_qtys = listaddressesbyasset(asset) + + address_count = 0 + for address, qty in address_qtys.items(): + address_count = address_count + 1 + print(address + " -> " + str(qty)) + total_for_asset += qty + + print("Total in addresses for asset " + asset + " is " + str(total_for_asset)) + + #Calculate stats + if address_count > max_dist_address_count: + max_dist_asset_name = asset + max_dist_address_count = address_count + + if (total_issued == total_for_asset): + print("Audit PASSED for " + asset) + print("") + else: + print("Audit FAILED for " + asset) + exit() + + if len(assets) == count: + print("All " + str(len(assets)) + " assets audited.") + print("Stats:") + print(" Max Distribed Asset: " + max_dist_asset_name + " with " + str(max_dist_address_count) + " addresses.") + + + +if mode == "-regtest": #If regtest then mine our own blocks + import os + os.system(cli + " " + mode + " generate 400") + +audit("*") #Set to "*" for all. diff --git a/assets/tools/blockfacts.py b/assets/tools/blockfacts.py new file mode 100644 index 0000000000..1bc084fcda --- /dev/null +++ b/assets/tools/blockfacts.py @@ -0,0 +1,45 @@ +#Shows data from the first 1000 blocks + +import random +import os +import subprocess +import json + + +#Set this to your raven-cli program +cli = "raven-cli" + +#mode = "-testnet" +mode = "" +rpc_port = 8766 +#Set this information in your raven.conf file (in datadir, not testnet3) +rpc_user = 'rpcuser' +rpc_pass = 'rpcpass555' + + +def rpc_call(params): + process = subprocess.Popen([cli, mode, params], stdout=subprocess.PIPE) + out, err = process.communicate() + return(out) + +def get_blockinfo(num): + rpc_connection = get_rpc_connection() + hash = rpc_connection.getblockhash(num) + blockinfo = rpc_connection.getblock(hash) + return(blockinfo) + +def get_rpc_connection(): + from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + connection = "http://%s:%s@127.0.0.1:%s"%(rpc_user, rpc_pass, rpc_port) + #print("Connection: " + connection) + rpc_connection = AuthServiceProxy(connection) + return(rpc_connection) + +for i in range(1,1000): + dta = get_blockinfo(i) + print("Block #" + str(i)) + print(dta.get('hash')) + print(dta.get('difficulty')) + print(dta.get('time')) + print("") + diff --git a/assets/tools/issuebulk.py b/assets/tools/issuebulk.py index 7a2fe08b74..630fc59f11 100644 --- a/assets/tools/issuebulk.py +++ b/assets/tools/issuebulk.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Script to issue assets on the Ravencoin platform # Reads from a csv file # Template Google Spreadsheet at: diff --git a/assets/tools/signed_promises.py b/assets/tools/signed_promises.py index 3a75cd8964..06b0f29855 100644 --- a/assets/tools/signed_promises.py +++ b/assets/tools/signed_promises.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Script to find signed contract_urls # Reads from a Ravencoin node - make sure its running # Runs through the assets looking for ones with meta data diff --git a/assets/tools/txfacts.py b/assets/tools/txfacts.py new file mode 100644 index 0000000000..40c115c119 --- /dev/null +++ b/assets/tools/txfacts.py @@ -0,0 +1,120 @@ +#Shows data from the transactions + +import random +import os +import subprocess +import json + + +#Set this to your raven-cli program +cli = "raven-cli" + +mode = "-testnet" +mode = "" +rpc_port = 18766 +#Set this information in your raven.conf file (in datadir, not testnet3) +rpc_user = 'rpcuser' +rpc_pass = 'rpcpass555' + +def get_rpc_connection(): + from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + connection = "http://%s:%s@127.0.0.1:%s"%(rpc_user, rpc_pass, rpc_port) + rpc_conn = AuthServiceProxy(connection) + return(rpc_conn) + +rpc_connection = get_rpc_connection() + +def rpc_call(params): + process = subprocess.Popen([cli, mode, params], stdout=subprocess.PIPE) + out, err = process.communicate() + process.stdout.close() + process.stderr.close() + return(out) + +def get_blockinfo(num): + hash = rpc_connection.getblockhash(num) + blockinfo = rpc_connection.getblock(hash) + return(blockinfo) + +def get_block(hash): + blockinfo = rpc_connection.getblock(hash) + return(blockinfo) + +def get_rawtx(tx): + txinfo = rpc_connection.getrawtransaction(tx) + return(txinfo) + +def get_bci(): + bci = rpc_connection.getblockchaininfo() + return(bci) + +def decode_rawtx(txdata): + #print("decoding: " + txdata) + txjson = rpc_connection.decoderawtransaction(txdata) + return(txjson) + +def decode_script(script): + scriptinfo = rpc_connection.decodescript(script) + return(scriptinfo) + +def ipfs_add(file): + print("Adding to IPFS") + import ipfsapi + api = ipfsapi.connect('127.0.0.1', 5001) + res = api.add(file) + print(res) + return(res['Hash']) + +def ipfs_get(hash): + import ipfsapi + api = ipfsapi.connect('127.0.0.1', 5001) + res = api.get(hash) + return() + +def ipfs_pin_add(hash): + import ipfsapi + api = ipfsapi.connect('127.0.0.1', 5001) + res = api.pin_add(hash) + return(res) + +def asset_handler(asset_script): + # print("Type: " + asset_script.get('type')) + # print("Asset: " + asset_script.get('asset_name')) + # print(asset_script.get('amount')) + # print(asset_script.get('units')) + # print("Reissuable: " + str(asset_script.get('reissuable'))) + # print("Has IPFS: " + str(asset_script.get('hasIPFS'))) + if asset_script.get('hasIPFS') == True: + print(asset_script.get('ipfs_hash')) + ipfs_pin_add(asset_script.get('ipfs_hash')) + +#Get the blockheight of the chain +blockheight = get_bci().get('blocks') + +for i in range(23500,blockheight): + dta = get_blockinfo(i) + print("Block #" + str(i) + " - " + dta.get('hash')) + #print(dta.get('difficulty')) + #print(dta.get('time')) + + tx_in_block = get_block(dta.get('hash')) + txs = tx_in_block.get('tx') + #print(txs) + for tx in txs: + tx_info = get_rawtx(tx) + #print("txinfo: " + tx_info) + tx_detail = decode_rawtx(tx_info) + for vout in tx_detail.get('vout'): + #print("vout: " + str(vout.get('value'))) + #print(vout.get('scriptPubKey').get('asm')) + if (vout.get('scriptPubKey').get('asm')[86:98] == "OP_RVN_ASSET"): + #print("Found OP_RVN_ASSET") + #print(vout.get('scriptPubKey').get('hex')) + asset_script = decode_script(vout.get('scriptPubKey').get('hex')) + asset_handler(asset_script) + #print(asset_script) + #print("txdecoded: " + tx_detail.get('vout')) + + + #print("") + diff --git a/configure.ac b/configure.ac index 7c081bc1ce..b492e106d0 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,9 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 2) -define(_CLIENT_VERSION_MINOR, 0) -define(_CLIENT_VERSION_REVISION, 4) -define(_CLIENT_VERSION_BUILD, 1) +define(_CLIENT_VERSION_MINOR, 1) +define(_CLIENT_VERSION_REVISION, 0) +define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2018) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/contrib/devtools/clang-format-diff.py b/contrib/devtools/clang-format-diff.py index 7ea49b65e1..617ebd75cd 100755 --- a/contrib/devtools/clang-format-diff.py +++ b/contrib/devtools/clang-format-diff.py @@ -150,7 +150,7 @@ def main(): sys.exit(p.returncode) if not args.i: - with open(filename) as f: + with open(filename, encoding="utf8") as f: code = f.readlines() formatted_code = StringIO.StringIO(stdout).readlines() diff = difflib.unified_diff(code, formatted_code, diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index ca71664a45..1259d82109 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -145,7 +145,7 @@ def file_has_without_c_style_copyright_for_holder(contents, holder_name): ################################################################################ def read_file(filename): - return open(os.path.abspath(filename), 'r').read() + return open(os.path.abspath(filename), 'r', encoding="utf8").read() def gather_file_info(filename): info = {} @@ -324,13 +324,13 @@ def get_most_recent_git_change_year(filename): ################################################################################ def read_file_lines(filename): - f = open(os.path.abspath(filename), 'r') + f = open(os.path.abspath(filename), 'r', encoding="utf8") file_lines = f.readlines() f.close() return file_lines def write_file_lines(filename, file_lines): - f = open(os.path.abspath(filename), 'w') + f = open(os.path.abspath(filename), 'w', encoding="utf8") f.write(''.join(file_lines)) f.close() diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py index 4e81e5caba..df2139aaaa 100755 --- a/contrib/devtools/github-merge.py +++ b/contrib/devtools/github-merge.py @@ -190,7 +190,7 @@ def main(): merge_branch = 'pull/'+pull+'/merge' local_merge_branch = 'pull/'+pull+'/local-merge' - devnull = open(os.devnull,'w') + devnull = open(os.devnull, 'w', encoding="utf8") try: subprocess.check_call([GIT,'checkout','-q',branch]) except subprocess.CalledProcessError as e: diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index 7e9fd9a33e..fab0b874c9 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -11,7 +11,7 @@ import unittest def write_testcode(filename): - with open(filename, 'w') as f: + with open(filename, 'w', encoding="utf8") as f: f.write(''' #include int main() diff --git a/contrib/filter-lcov.py b/contrib/filter-lcov.py index 299377d691..df1db76e92 100755 --- a/contrib/filter-lcov.py +++ b/contrib/filter-lcov.py @@ -13,8 +13,8 @@ outfile = args.outfile in_remove = False -with open(tracefile, 'r') as f: - with open(outfile, 'w') as wf: +with open(tracefile, 'r', encoding="utf8") as f: + with open(outfile, 'w', encoding="utf8") as wf: for line in f: for p in pattern: if line.startswith("SF:") and p in line: diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index 7dbfea4c30..c168d27c5d 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -77,7 +77,7 @@ def get_blk_dt(blk_hdr): # When getting the list of block hashes, undo any byte reversals. def get_block_hashes(settings): blkindex = [] - f = open(settings['hashlist'], "r") + f = open(settings['hashlist'], "r", encoding="utf8") for line in f: line = line.rstrip() if settings['rev_hash_bytes'] == 'true': @@ -263,7 +263,7 @@ def run(self): print("Usage: linearize-data.py CONFIG-FILE") sys.exit(1) - f = open(sys.argv[1]) + f = open(sys.argv[1], encoding="utf8") for line in f: # skip comment lines m = re.search('^\s*#', line) diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py index c687528d29..1ad355cb51 100755 --- a/contrib/linearize/linearize-hashes.py +++ b/contrib/linearize/linearize-hashes.py @@ -98,7 +98,7 @@ def get_block_hashes(settings, max_blocks_per_call=10000): def get_rpc_cookie(): # Open the cookie file - with open(os.path.join(os.path.expanduser(settings['datadir']), '.cookie'), 'r') as f: + with open(os.path.join(os.path.expanduser(settings['datadir']), '.cookie'), 'r', encoding="ascii") as f: combined = f.readline() combined_split = combined.split(":") settings['rpcuser'] = combined_split[0] @@ -109,7 +109,7 @@ def get_rpc_cookie(): print("Usage: linearize-hashes.py CONFIG-FILE") sys.exit(1) - f = open(sys.argv[1]) + f = open(sys.argv[1], encoding="utf8") for line in f: # skip comment lines m = re.search('^\s*#', line) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index 70cd4904fd..35a575d6d6 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -126,10 +126,10 @@ def main(): g.write(' * Each line contains a 16-byte IPv6 address and a port.\n') g.write(' * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly.\n') g.write(' */\n') - with open(os.path.join(indir,'nodes_main.txt'),'r') as f: + with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'pnSeed6_main', 8767) g.write('\n') - with open(os.path.join(indir,'nodes_test.txt'),'r') as f: + with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'pnSeed6_test', 18767) g.write('#endif // RAVEN_CHAINPARAMSSEEDS_H\n') diff --git a/contrib/zmq/zmq_test.py b/contrib/zmq/zmq_test.py new file mode 100644 index 0000000000..eb82ba4370 --- /dev/null +++ b/contrib/zmq/zmq_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Copyright (c) 2017 The Raven Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" + ZMQ example using python3's asyncio + + Raven should be started with the command line arguments: + ravend -testnet -daemon \ + -zmqpubhashblock=tcp://127.0.0.1:28766 \ + -zmqpubrawtx=tcp://127.0.0.1:28766 \ + -zmqpubhashtx=tcp://127.0.0.1:28766 \ + -zmqpubhashblock=tcp://127.0.0.1:28766 +""" + +import sys +import zmq +import struct +import binascii +import codecs + +# Socket to talk to server +context = zmq.Context() +socket = context.socket(zmq.SUB) + +print("Getting Ravencoin msgs") +socket.connect("tcp://localhost:28766") + +socket.setsockopt_string(zmq.SUBSCRIBE, "hashtx") +socket.setsockopt_string(zmq.SUBSCRIBE, "hashblock") +socket.setsockopt_string(zmq.SUBSCRIBE, "rawblock") +socket.setsockopt_string(zmq.SUBSCRIBE, "rawtx") + +while True: + msg = socket.recv_multipart() + topic = msg[0] + body = msg[1] + sequence = "Unknown" + if len(msg[-1]) == 4: + msgSequence = struct.unpack(' -1): + print("FOUND RVN issuance at " + str(pos)) + print("After RVN: " + astr[pos+6:pos+8]) + sizestr = astr[pos+8:pos+10] + print("sizestr: " + sizestr) + #print(str(astr[pos+8:pos+10])) + size = int(sizestr, 16) + print("Bytes: " + str(size)) + print("Name: " + bytes.fromhex(astr[pos+10:pos+10+size*2]).decode('utf-8')) + pos = astr.find('72766e', start) + if (pos > -1): + print("FOUND RVN something at " + str(pos)) + start += pos+8 + print(astr) + + diff --git a/doc/README_windows.txt b/doc/README_windows.txt index 4b9efffb8b..8f06194e4f 100644 --- a/doc/README_windows.txt +++ b/doc/README_windows.txt @@ -19,5 +19,5 @@ depending on the speed of your computer and network connection, the synchronizat process can take anywhere from a few hours to a day or more. See the raven wiki at: - https://en.raven.it/wiki/Main_Page + https://raven.wiki/wiki/Ravencoin_Wiki for more help and information. diff --git a/doc/adr/0001-record-architecture-decisions.md b/doc/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000000..04bad3a9f6 --- /dev/null +++ b/doc/adr/0001-record-architecture-decisions.md @@ -0,0 +1,19 @@ +# 1. Record architecture decisions + +Date: 2018-09-04 + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools). diff --git a/doc/atomicswaps.md b/doc/atomicswaps.md new file mode 100644 index 0000000000..b90231b382 --- /dev/null +++ b/doc/atomicswaps.md @@ -0,0 +1,122 @@ +# HOWTO: Atomic Swaps + +__Or, trading apples for bananas.__ + + +## The Problem + +_Andy has some apples. Barb has some bananas. Andy agrees to give Barb two apples in exchange for one banana. In order to make sure both parties hold up their end of the deal they need to create a single transaction that sends assets both directions._ + +## The Inputs + +We'll need to find 3 UTXOs to use as inputs. Andy's apples, Barb's bananas and some Raven to pay the network fee (Andy will pay). + +Andy uses `listunspent` to find a suitable `txid` and `vout`: + +``` +{ + "txid": "631cf1566165803f0b89fbfb169d8f0c89129ec3f8536a48e4c4f0f3c4081cff", + "vout": 0, + "address": "mh8mmbCPqnxCNqJq547NG99pauTHqvvYjA", + "scriptPubKey": "21027e1fde02d2cbfac3629aeaf669abd156d0c4dfbf52f6a5e4dd6664e81a621045ac", + "amount": 4.88281250, + "confirmations": 3088, + "spendable": true, + "solvable": true, + "safe": true +} +``` + +Then each party uses `listmyassets` with `verbose=true` to find asset UTXOs: + +Andy: +`listmyassets APPLES true`: + +``` +{ + "APPLES": { + "balance": 1000, + "outpoints": [ + { + "txid": "744c61abe6d237939c3567bc44b912ff4c375984229908c964be44f36dec79e3", + "vout": 3, + "amount": 1000 + } + ] + } +} +``` + +Barb: +`listmyassets BANANAS true`: + +``` +{ + "BANANAS": { + "balance": 1000, + "outpoints": [ + { + "txid": "8468ef5193b1f5d6a7bd501f5f8ef5aec7c3d86fa87cec5a0b6f6d86fba78a4f", + "vout": 3, + "amount": 1000 + } + ] + } +} +``` + +Extracting the txids and vouts gives us our raw inputs: +`'[{"txid":"631cf1566165803f0b89fbfb169d8f0c89129ec3f8536a48e4c4f0f3c4081cff","vout":0}, \ + {"txid":"744c61abe6d237939c3567bc44b912ff4c375984229908c964be44f36dec79e3","vout":3}, \ + {"txid":"8468ef5193b1f5d6a7bd501f5f8ef5aec7c3d86fa87cec5a0b6f6d86fba78a4f","vout":3}]'` + +## The Outputs + +We'll be using 5 new Ravencoin addresses: + +Andy's Raven change address: +`mvGfeg4uZA8XvjVDUywdgYE6TAyz77o5gB` + +Andy's banana receive address: +`msXQpCK8UexfgtMbUGwnKjDfE6vqJ4JUPF` + +Barb's banana change address: +`mjyoMtEtoxw9edgzpNnEVTH7jqSqiQ529N` + +Barb's apple receive address: +`mzPe6rUPYcbDbqnxRc5mcvKkASCAS9JBzL` + +Andy's apple change address: +`mzct8GQ5zdaCvbrnRDrR8T87ZuZxkRYNwL` + +All asset transfers have to be balanced. Since we have 1000 APPLES coming in, we have to have 1000 going out. So Andy will send 2 to Barb's receive address and the rest (998) to his change address. The Raven will pay 0.0001 for the network fee as normal. + +`'{"mvGfeg4uZA8XvjVDUywdgYE6TAyz77o5gB":4.8827125, \ + "msXQpCK8UexfgtMbUGwnKjDfE6vqJ4JUPF":{"transfer":{"BANANAS":1}}, \ + "mjyoMtEtoxw9edgzpNnEVTH7jqSqiQ529N":{"transfer":{"BANANAS":999}}, \ + "mzPe6rUPYcbDbqnxRc5mcvKkASCAS9JBzL":{"transfer":{"APPLES":2}}, \ + "mzct8GQ5zdaCvbrnRDrR8T87ZuZxkRYNwL":{"transfer":{"APPLES":998}}}'` + +## Creating The Transaction + +Andy can now use `createrawtransaction`, passing in the inputs and outputs and receiving the transaction hex: + +``` +createrawtransaction '[{"txid":"631cf1566165803f0b89fbfb169d8f0c89129ec3f8536a48e4c4f0f3c4081cff","vout":0},{"txid":"744c61abe6d237939c3567bc44b912ff4c375984229908c964be44f36dec79e3","vout":3},{"txid":"8468ef5193b1f5d6a7bd501f5f8ef5aec7c3d86fa87cec5a0b6f6d86fba78a4f","vout":3}]' '{"mvGfeg4uZA8XvjVDUywdgYE6TAyz77o5gB":4.8827125,"msXQpCK8UexfgtMbUGwnKjDfE6vqJ4JUPF":{"transfer":{"BANANAS":1}},"mjyoMtEtoxw9edgzpNnEVTH7jqSqiQ529N":{"transfer":{"BANANAS":999}},"mzPe6rUPYcbDbqnxRc5mcvKkASCAS9JBzL":{"transfer":{"APPLES":2}},"mzct8GQ5zdaCvbrnRDrR8T87ZuZxkRYNwL":{"transfer":{"APPLES":998}}}' +``` + +``` +0200000003ff1c08c4f3f0c4e4486a53f8c39e12890c8f9d16fbfb890b3f80656156f11c630000000000ffffffffe379ec6df344be64c90899228459374cff12b944bc67359c9337d2e6ab614c740300000000ffffffff4f8aa7fb866d6f0b5aec7ca86fd8c3c7aef58e5f1f50bda7d6f5b19351ef68840300000000ffffffff05926d1a1d000000001976a914a1d62b4f8a6710ad43d56f656c27966513ae8fbf88ac00000000000000003076a91483b7a59ce25d8159fdc2ab70320512d2bc07a1c488acc01472766e740742414e414e415300e1f505000000007500000000000000003076a91430f446c2ac37dc072516603e44e86aea7af2bf6788acc01472766e740742414e414e415300078142170000007500000000000000002f76a914cf084a93cf87d3030b74a1939d9c8f02f6152c8c88acc01372766e74064150504c455300c2eb0b000000007500000000000000002f76a914d18967e005ff7208cc6a9c483b028c7c72ca2d6e88acc01372766e74064150504c455300268b3c170000007500000000 +``` + +## Signing The Transaction + +Ok, the structure of the transaction is set. Now each party needs to sign it in order to unlock the inputs. Here are the steps: + +* Andy signs it using `signrawtransaction`. This will alter the hex, using his wallet to insert the signatures. +* Andy sends the signed hex to Barb. +* Barb uses `signrawtransaction` to sign the rest of the inputs. Again capture the hex output. + +## Submit The Transaction + +Almost there. Barb now passes the fully signed hex to `sendrawtransaction`! It will be communicated to the network and put into the next block. diff --git a/doc/build-osx.md b/doc/build-osx.md index 6aebfbe606..53c32d13c0 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -26,6 +26,26 @@ If you want to build the disk image with `make deploy` (.dmg / optional), you ne NOTE: Building with Qt4 is still supported, however, could result in a broken UI. Building with Qt5 is recommended. +NOTE: At this time it is highly recommended that developers wishing to compile the Raven Core binaries **DO NOT** upgrade to +OS X Mojave Beta 10.14. Currently there is a compatibility issue with OS X Mojave, Command-Line-Tools 10.0.0 (clang), and +Berkeley-db version 4.8.3. Binaries compiled using this combination will crash with a segmentation-fault during initialization. +Binaries compiled by previous versions will run on OS X Mojave with no-known issues. It is possible to work-around this issue by +upgrading Berkeley-db to version 18.1.25 or newer (currently 18.1.25 is the only known version to work). To compile and run with +newer versions of Berkeley-db it is recommended that Berkeley-db 4.8.3 be uninstalled and the latest version installed. There are +unknown wallet compatability ramifications to this solution so it is highly recommended that any local wallets be backed-up before +opening them using binaries compiled with this solution. + +Use the following commands to compile a working version of Raven Core on Mojave (assuming that the instructions in the section "Build +Raven Core" has already been followed). Uninstall Berkeley-db 4.8.3, install the latest version, and _configure_ with the +incompatible-bdb flag: + + brew remove berkeley-db@4 + brew install bekeley-db + ./autogen.sh + ./configure --with-incompatible-bdb + make + + Build Raven Core ------------------------ @@ -96,7 +116,7 @@ Uncheck everything except Qt Creator during the installation process. Notes ----- -* Tested on OS X 10.8 through 10.12 on 64-bit Intel processors only. +* Tested on OS X 10.8 through 10.14 on 64-bit Intel processors only. * Building with downloaded Qt binaries is not officially supported. diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 7bcebff37d..4c2ab989e0 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -693,3 +693,8 @@ A few guidelines for introducing and reviewing new RPC interfaces: - *Rationale*: If a RPC response is not a JSON object then it is harder to avoid API breakage if new data in the response is needed. + + +IRC Required Commit +--------------------- +Required to register on IRC diff --git a/doc/fuzzing.md b/doc/fuzzing.md index 6fd5effbfb..da9b2e716b 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -55,7 +55,7 @@ AFLOUT=$PWD/outputs Example inputs are available from: -- https://download.visucore.com/raven/raven_fuzzy_in.tar.xz +- https://download.visucore.com/bitcoin/bitcoin_fuzzy_in.tar.xz - http://strateman.ninja/fuzzing.tar.xz Extract these (or other starting inputs) into the `inputs` directory before starting fuzzing. diff --git a/doc/release-notes-pr12924.md b/doc/release-notes-pr12924.md new file mode 100644 index 0000000000..5bf45194b8 --- /dev/null +++ b/doc/release-notes-pr12924.md @@ -0,0 +1,11 @@ +RPC changes +------------ + +### Low-level changes + +- The `getwalletinfo` RPC method now returns an `hdseedid` value, which is always the same as the incorrectly-named `hdmasterkeyid` value. `hdmasterkeyid` will be removed in V0.18. + +Other API changes +----------------- + +- The `inactivehdmaster` property in the `dumpwallet` output has been corrected to `inactivehdseed` diff --git a/doc/release-notes/release-notes-2.0.4.md b/doc/release-notes/release-notes-2.0.4.md new file mode 100644 index 0000000000..c5507fbf07 --- /dev/null +++ b/doc/release-notes/release-notes-2.0.4.md @@ -0,0 +1,87 @@ +Raven Core version *2.0.4.0* is now available!! +============== + + + + +This is a major release containing bug fixes for 2.0.3.0. It is highly recommended that users +upgrade to this version. + +Please report bugs using the issue tracker at GitHub: + + + +To receive security and update notifications, please subscribe to: + + + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over `/Applications/Raven-Qt` (on Mac) +or `ravend`/`raven-qt` (on Linux). + +The first time you run version 2.0.4 or higher, your chainstate database may +be converted to a new format, which will take anywhere from a few minutes to +half an hour, depending on the speed of your machine. + +Downgrading warning +============== + +The chainstate database for this release is not compatible with previous +releases, so if you run 2.0.4 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +It is not recommended that users downgrade their version. This version contains +changes that *will* fork the chain, users not running 2.0.4 (or later) will be not +be able to participate in this fork process and will be left on the old chain which +will not be valid. + +Compatibility +============== + +Raven Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.8+, and Windows Vista and later. 32-bit versions of Windows, +and Windows XP are not supported. + +Raven Core should also work on most other Unix-like systems but is not +frequently tested on them. + +Notable changes +============== + +- Fix testnet chain syncing. +- Increase chaining depth. +- Fix for -txindex after immediate BIP activation +- Fix null pointer bug. +- Fix qt amount formatting. +- Python script for bulk issuance in assets/tools. +- Python script for finding signed meta data. + + +2.0.4.0-b175d7350 Change log +============== + +Changelog available here: + +Credits +============== + +Thanks to everyone who directly contributed to this release: + +- Most importantly - The Raven Community! +- Tron Black +- Jesse Empey +- Jeremy Anderson +- Corbin Fox +- Daben Steele +- Cade Call +- @Roshii +- @underdarkskies +- Mark Ney diff --git a/doc/release-notes/release-notes-2.1.0.md b/doc/release-notes/release-notes-2.1.0.md new file mode 100644 index 0000000000..673340f357 --- /dev/null +++ b/doc/release-notes/release-notes-2.1.0.md @@ -0,0 +1,94 @@ +Raven Core version *2.1.0* is now available!! +============== + + + + +This is a major release containing bug fixes for 2.0.4.0/2.0.4.1. It is highly recommended that users +upgrade to this version. This is the final release for the phase 2 development (assets). + +Please report bugs using the issue tracker at GitHub: + + + +To receive security and update notifications, please subscribe to: + + + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over `/Applications/Raven-Qt` (on Mac) +or `ravend`/`raven-qt` (on Linux). + +The first time you run version 2.1.0 or higher, your chainstate database may +be converted to a new format, which will take anywhere from a few minutes to +half an hour, depending on the speed of your machine. + +Downgrading warning +============== + +The chainstate database for this release is not compatible with previous +releases, so if you run 2.1.0 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +It is not recommended that users downgrade their version. This version contains +changes that *will* fork the chain, users not running 2.1.0 (or later) will be not +be able to participate in this fork process and will be left on the old chain which +will not be valid. + +Compatibility +============== + +Raven Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.8+, and Windows Vista and later. 32-bit versions of Windows, +and Windows XP are not supported. + +Raven Core should also work on most other Unix-like systems but is not +frequently tested on them. + +Raven Core has been tested with macOS 10.14 Mojave, but it is recommended that developers +do not update to Mojave. There is an incompatibility with Berkeley-db 4.8.30 that causes +the binaries to seg-fault. There is a workaround, but as of this release users should +not update to Mojave (see build-OSX.md for current status of this issue). There are no +known issues running the release binaries on Mojave. + +Notable changes +============== + +- Mainnet asset activation (Voting begins October 31, 2018) +- Double-spend attack mitigation +- Many QT Wallet UI enhancement +- Removed Replace by Fee (RBF) +- Functional test overhaul, added tests for new features +- Reissue with zero amount (with owner token) +- Moved testnet to v6 +- Added asset transaction chaining +- Chain synchronization stability + +2.1.0 Change log +============== + +Changelog available here: + +Credits +============== + +Thanks to everyone who directly contributed to this release: + +- Most importantly - The Raven Community! +- Tron Black +- Jesse Empey +- Jeremy Anderson +- Corbin Fox +- Daben Steele +- Cade Call +- @Roshii +- @underdarkskies +- Mark Ney diff --git a/share/qt/extract_strings_qt.py b/share/qt/extract_strings_qt.py index a777261e01..2261441663 100755 --- a/share/qt/extract_strings_qt.py +++ b/share/qt/extract_strings_qt.py @@ -65,7 +65,7 @@ def parse_po(text): messages = parse_po(out.decode('utf-8')) -f = open(OUT_CPP, 'w') +f = open(OUT_CPP, 'w', encoding="utf8") f.write(""" #include diff --git a/src/Makefile.am b/src/Makefile.am index a1e7fb3a25..9662b50d8f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -214,6 +214,7 @@ libraven_server_a_SOURCES = \ noui.cpp \ assets/assets.cpp \ assets/assetdb.cpp \ + assets/assettypes.cpp \ policy/fees.cpp \ policy/policy.cpp \ policy/rbf.cpp \ @@ -356,6 +357,8 @@ libraven_consensus_a_SOURCES = \ script/script_error.cpp \ script/script_error.h \ serialize.h \ + support/cleanse.h \ + support/cleanse.cpp \ tinyformat.h \ uint256.cpp \ uint256.h \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 57b183151d..a15dcef602 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -98,6 +98,7 @@ QT_TS = \ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ + qt/forms/assetcontroldialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ @@ -122,6 +123,8 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_assetcontroldialog.cpp \ + qt/moc_assetcontroltreewidget.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_ravenaddressvalidator.cpp \ qt/moc_ravenamountfield.cpp \ @@ -196,6 +199,8 @@ RAVEN_QT_H = \ qt/addressbookpage.h \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ + qt/assetcontroldialog.h \ + qt/assetcontroltreewidget.h \ qt/assetsdialog.h \ qt/createassetdialog.h \ qt/bantablemodel.h \ @@ -257,6 +262,7 @@ RES_ICONS = \ qt/res/icons/address-book.png \ qt/res/icons/about.png \ qt/res/icons/about_qt.png \ + qt/res/icons/asset_administrator.png \ qt/res/icons/raven.ico \ qt/res/icons/raven_testnet.ico \ qt/res/icons/raven.png \ @@ -343,6 +349,8 @@ RAVEN_QT_WALLET_CPP = \ qt/addressbookpage.cpp \ qt/addresstablemodel.cpp \ qt/askpassphrasedialog.cpp \ + qt/assetcontroldialog.cpp \ + qt/assetcontroltreewidget.cpp \ qt/assetsdialog.cpp \ qt/createassetdialog.cpp \ qt/coincontroldialog.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e920695adb..1f773b2a95 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -26,6 +26,7 @@ GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.r # test_raven binary # RAVEN_TESTS =\ test/assets/asset_tests.cpp \ + test/assets/serialization_tests.cpp \ test/assets/asset_tx_tests.cpp \ test/assets/cache_tests.cpp \ test/assets/asset_reissue_tests.cpp \ diff --git a/src/assets/assetdb.cpp b/src/assets/assetdb.cpp index 2658bb785c..79fc7a3113 100644 --- a/src/assets/assetdb.cpp +++ b/src/assets/assetdb.cpp @@ -16,13 +16,15 @@ static const char ASSET_FLAG = 'A'; static const char ASSET_ADDRESS_QUANTITY_FLAG = 'B'; static const char MY_ASSET_FLAG = 'M'; static const char BLOCK_ASSET_UNDO_DATA = 'U'; +static const char MEMPOOL_REISSUED_TX = 'Z'; CAssetsDB::CAssetsDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "assets", nCacheSize, fMemory, fWipe) { } -bool CAssetsDB::WriteAssetData(const CNewAsset &asset) +bool CAssetsDB::WriteAssetData(const CNewAsset &asset, const int nHeight, const uint256& blockHash) { - return Write(std::make_pair(ASSET_FLAG, asset.strName), asset); + CDatabasedAssetData data(asset, nHeight, blockHash); + return Write(std::make_pair(ASSET_FLAG, asset.strName), data); } bool CAssetsDB::WriteMyAssetsData(const std::string &strName, const std::set& setOuts) @@ -35,9 +37,19 @@ bool CAssetsDB::WriteAssetAddressQuantity(const std::string &assetName, const st return Write(std::make_pair(ASSET_ADDRESS_QUANTITY_FLAG, std::make_pair(assetName, address)), quantity); } -bool CAssetsDB::ReadAssetData(const std::string& strName, CNewAsset& asset) +bool CAssetsDB::ReadAssetData(const std::string& strName, CNewAsset& asset, int& nHeight, uint256& blockHash) { - return Read(std::make_pair(ASSET_FLAG, strName), asset); + + CDatabasedAssetData data; + bool ret = Read(std::make_pair(ASSET_FLAG, strName), data); + + if (ret) { + asset = data.asset; + nHeight = data.nHeight; + blockHash = data.blockHash; + } + + return ret; } bool CAssetsDB::ReadMyAssetsData(const std::string &strName, std::set& setOuts) @@ -72,22 +84,39 @@ bool CAssetsDB::EraseMyOutPoints(const std::string& assetName) return true; } -bool CAssetsDB::WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& vIPFSHashes) +bool CAssetsDB::WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& assetUndoData) { - return Write(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), vIPFSHashes); + return Write(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), assetUndoData); } -bool CAssetsDB::ReadBlockUndoAssetData(const uint256 &blockhash, - std::vector > &vIPFSHashes) { - +bool CAssetsDB::ReadBlockUndoAssetData(const uint256 &blockhash, std::vector > &assetUndoData) +{ // If it exists, return the read value. if (Exists(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash))) - return Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), vIPFSHashes); + return Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), assetUndoData); // If it doesn't exist, we just return true because we don't want to fail just because it didn't exist in the db return true; } +bool CAssetsDB::WriteReissuedMempoolState() +{ + return Write(MEMPOOL_REISSUED_TX, mapReissuedAssets); +} + +bool CAssetsDB::ReadReissuedMempoolState() +{ + mapReissuedAssets.clear(); + mapReissuedTx.clear(); + // If it exists, return the read value. + bool rv = Read(MEMPOOL_REISSUED_TX, mapReissuedAssets); + if (rv) { + for (auto pair : mapReissuedAssets) + mapReissuedTx.insert(std::make_pair(pair.second, pair.first)); + } + return rv; +} + bool CAssetsDB::LoadAssets() { std::unique_ptr pcursor(NewIterator()); @@ -99,9 +128,9 @@ bool CAssetsDB::LoadAssets() boost::this_thread::interruption_point(); std::pair key; if (pcursor->GetKey(key) && key.first == ASSET_FLAG) { - CNewAsset asset; - if (pcursor->GetValue(asset)) { - passetsCache->Put(asset.strName, asset); + CDatabasedAssetData data; + if (pcursor->GetValue(data)) { + passetsCache->Put(data.asset.strName, data); pcursor->Next(); } else { return error("%s: failed to read asset", __func__); @@ -158,7 +187,7 @@ bool CAssetsDB::LoadAssets() return true; } -bool CAssetsDB::AssetDir(std::vector& assets, const std::string filter, const size_t count, const long start) +bool CAssetsDB::AssetDir(std::vector& assets, const std::string filter, const size_t count, const long start) { FlushStateToDisk(); @@ -211,9 +240,9 @@ bool CAssetsDB::AssetDir(std::vector& assets, const std::string filte offset += 1; } else { - CNewAsset asset; - if (pcursor->GetValue(asset)) { - assets.push_back(asset); + CDatabasedAssetData data; + if (pcursor->GetValue(data)) { + assets.push_back(data); loaded += 1; } else { return error("%s: failed to read asset", __func__); @@ -229,7 +258,7 @@ bool CAssetsDB::AssetDir(std::vector& assets, const std::string filte return true; } -bool CAssetsDB::AssetDir(std::vector& assets) +bool CAssetsDB::AssetDir(std::vector& assets) { return CAssetsDB::AssetDir(assets, "*", MAX_SIZE, 0); } \ No newline at end of file diff --git a/src/assets/assetdb.h b/src/assets/assetdb.h index 97afb08427..8b9c94e3ef 100644 --- a/src/assets/assetdb.h +++ b/src/assets/assetdb.h @@ -15,6 +15,25 @@ class CNewAsset; class uint256; class COutPoint; +class CDatabasedAssetData; + +struct CBlockAssetUndo +{ + bool fChangedIPFS; + bool fChangedUnits; + std::string strIPFS; + int nUnits; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(fChangedUnits); + READWRITE(fChangedIPFS); + READWRITE(strIPFS); + READWRITE(nUnits); + } +}; /** Access to the block database (blocks/index/) */ class CAssetsDB : public CDBWrapper @@ -26,16 +45,18 @@ class CAssetsDB : public CDBWrapper CAssetsDB& operator=(const CAssetsDB&) = delete; // Write to database functions - bool WriteAssetData(const CNewAsset& asset); + bool WriteAssetData(const CNewAsset& asset, const int nHeight, const uint256& blockHash); bool WriteMyAssetsData(const std::string &strName, const std::set& setOuts); bool WriteAssetAddressQuantity(const std::string& assetName, const std::string& address, const CAmount& quantity); - bool WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& vIPFSHashes); + bool WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& assetUndoData); + bool WriteReissuedMempoolState(); // Read from database functions - bool ReadAssetData(const std::string& strName, CNewAsset& asset); + bool ReadAssetData(const std::string& strName, CNewAsset& asset, int& nHeight, uint256& blockHash); bool ReadMyAssetsData(const std::string &strName, std::set& setOuts); bool ReadAssetAddressQuantity(const std::string& assetName, const std::string& address, CAmount& quantity); - bool ReadBlockUndoAssetData(const uint256& blockhash, std::vector >& vIPFSHashes); + bool ReadBlockUndoAssetData(const uint256& blockhash, std::vector >& assetUndoData); + bool ReadReissuedMempoolState(); // Erase from database functions bool EraseAssetData(const std::string& assetName); @@ -45,8 +66,8 @@ class CAssetsDB : public CDBWrapper // Helper functions bool EraseMyOutPoints(const std::string& assetName); bool LoadAssets(); - bool AssetDir(std::vector& assets, const std::string filter, const size_t count, const long start); - bool AssetDir(std::vector& assets); + bool AssetDir(std::vector& assets, const std::string filter, const size_t count, const long start); + bool AssetDir(std::vector& assets); }; diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 44f4ee51b3..4013d5f0c4 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -26,14 +26,20 @@ #include "protocol.h" #include "wallet/coincontrol.h" #include "utilmoneystr.h" +#include "coins.h" +#include "wallet/wallet.h" + +std::map mapReissuedTx; +std::map mapReissuedAssets; // excluding owner tag ('!') -static const auto MAX_NAME_LENGTH = 30; +static const auto MAX_NAME_LENGTH = 31; +static const auto MAX_CHANNEL_NAME_LENGTH = 12; // min lengths are expressed by quantifiers static const std::regex ROOT_NAME_CHARACTERS("^[A-Z0-9._]{3,}$"); static const std::regex SUB_NAME_CHARACTERS("^[A-Z0-9._]+$"); -static const std::regex UNIQUE_TAG_CHARACTERS("^[-A-Za-z0-9@$%&*()[\\]{}<>_.;?\\\\:]+$"); +static const std::regex UNIQUE_TAG_CHARACTERS("^[-A-Za-z0-9@$%&*()[\\]{}_.?:]+$"); static const std::regex CHANNEL_TAG_CHARACTERS("^[A-Z0-9._]+$"); static const std::regex VOTE_TAG_CHARACTERS("^[A-Z0-9._]+$"); @@ -51,7 +57,7 @@ static const std::regex CHANNEL_INDICATOR(R"(^[^^~#!]+~[^~#!\/]+$)"); static const std::regex OWNER_INDICATOR(R"(^[^^~#!]+!$)"); static const std::regex VOTE_INDICATOR(R"(^[^^~#!]+\^[^~#!\/]+$)"); -static const std::regex RAVEN_NAMES("^RVN$|^RAVEN$|^RAVENCOIN$|^RAVENC0IN$|^RAVENCO1N$|^RAVENC01N$"); +static const std::regex RAVEN_NAMES("^RVN$|^RAVEN$|^RAVENCOIN$"); bool IsRootNameValid(const std::string& name) { @@ -116,60 +122,63 @@ bool IsAssetNameASubasset(const std::string& name) return parts.size() > 1; } -bool IsAssetNameValid(const std::string& name, AssetType& assetType) +bool IsAssetNameValid(const std::string& name, AssetType& assetType, std::string& error) { - assetType = INVALID; + assetType = AssetType::INVALID; if (std::regex_match(name, UNIQUE_INDICATOR)) { - if (name.size() > MAX_NAME_LENGTH) return false; - std::vector parts; - boost::split(parts, name, boost::is_any_of(UNIQUE_TAG_DELIMITER)); - bool valid = IsNameValidBeforeTag(parts.front()) && IsUniqueTagValid(parts.back()); - if (!valid) return false; - assetType = AssetType::UNIQUE; - return true; + bool ret = IsTypeCheckNameValid(AssetType::UNIQUE, name, error); + if (ret) + assetType = AssetType::UNIQUE; + + return ret; } else if (std::regex_match(name, CHANNEL_INDICATOR)) { - if (name.size() > MAX_NAME_LENGTH) return false; - std::vector parts; - boost::split(parts, name, boost::is_any_of(CHANNEL_TAG_DELIMITER)); - bool valid = IsNameValidBeforeTag(parts.front()) && IsChannelTagValid(parts.back()); - if (!valid) return false; - assetType = AssetType::MSGCHANNEL; - return true; + bool ret = IsTypeCheckNameValid(AssetType::MSGCHANNEL, name, error); + if (ret) + assetType = AssetType::MSGCHANNEL; + + return ret; } else if (std::regex_match(name, OWNER_INDICATOR)) { - if (name.size() > MAX_NAME_LENGTH + 1) return false; - bool valid = IsNameValidBeforeTag(name.substr(0, name.size() - 1)); - if (!valid) return false; - assetType = AssetType::OWNER; - return true; - } else if (std::regex_match(name, VOTE_INDICATOR)) + bool ret = IsTypeCheckNameValid(AssetType::OWNER, name, error); + if (ret) + assetType = AssetType::OWNER; + + return ret; + } + else if (std::regex_match(name, VOTE_INDICATOR)) { - if (name.size() > MAX_NAME_LENGTH) return false; - std::vector parts; - boost::split(parts, name, boost::is_any_of(VOTE_TAG_DELIMITER)); - bool valid = IsNameValidBeforeTag(parts.front()) && IsVoteTagValid(parts.back()); - if (!valid) return false; - assetType = AssetType::VOTE; - return true; + bool ret = IsTypeCheckNameValid(AssetType::VOTE, name, error); + if (ret) + assetType = AssetType::VOTE; + + return ret; } else { - if (name.size() > MAX_NAME_LENGTH) return false; - bool valid = IsNameValidBeforeTag(name); - if (!valid) return false; - assetType = IsAssetNameASubasset(name) ? AssetType::SUB : AssetType::ROOT; - return true; + auto type = IsAssetNameASubasset(name) ? AssetType::SUB : AssetType::ROOT; + bool ret = IsTypeCheckNameValid(type, name, error); + if (ret) + assetType = type; + + return ret; } } bool IsAssetNameValid(const std::string& name) { AssetType _assetType; - return IsAssetNameValid(name, _assetType); + std::string _error; + return IsAssetNameValid(name, _assetType, _error); +} + +bool IsAssetNameValid(const std::string& name, AssetType& assetType) +{ + std::string _error; + return IsAssetNameValid(name, assetType, _error); } bool IsAssetNameAnOwner(const std::string& name) @@ -177,6 +186,46 @@ bool IsAssetNameAnOwner(const std::string& name) return IsAssetNameValid(name) && std::regex_match(name, OWNER_INDICATOR); } +// TODO get the string translated below +bool IsTypeCheckNameValid(const AssetType type, const std::string& name, std::string& error) +{ + if (type == AssetType::UNIQUE) { + if (name.size() > MAX_NAME_LENGTH) { error = "Name is greater than max length of " + std::to_string(MAX_NAME_LENGTH); return false; } + std::vector parts; + boost::split(parts, name, boost::is_any_of(UNIQUE_TAG_DELIMITER)); + bool valid = IsNameValidBeforeTag(parts.front()) && IsUniqueTagValid(parts.back()); + if (!valid) { error = "Unique name contains invalid characters (Valid characters are: A-Z a-z 0-9 @ $ % & * ( ) [ ] { } _ . ? : -)"; return false; } + return true; + } else if (type == AssetType::MSGCHANNEL) { + if (name.size() > MAX_NAME_LENGTH) { error = "Name is greater than max length of " + std::to_string(MAX_NAME_LENGTH); return false; } + std::vector parts; + boost::split(parts, name, boost::is_any_of(CHANNEL_TAG_DELIMITER)); + bool valid = IsNameValidBeforeTag(parts.front()) && IsChannelTagValid(parts.back()); + if (parts.back().size() > MAX_CHANNEL_NAME_LENGTH) { error = "Channel name is greater than max length of " + std::to_string(MAX_CHANNEL_NAME_LENGTH); return false; } + if (!valid) { error = "Message Channel name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can't be the first or last characters)"; return false; } + return true; + } else if (type == AssetType::OWNER) { + if (name.size() > MAX_NAME_LENGTH) { error = "Name is greater than max length of " + std::to_string(MAX_NAME_LENGTH); return false; } + bool valid = IsNameValidBeforeTag(name.substr(0, name.size() - 1)); + if (!valid) { error = "Owner name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can't be the first or last characters)"; return false; } + return true; + } else if (type == AssetType::VOTE) { + if (name.size() > MAX_NAME_LENGTH) { error = "Name is greater than max length of " + std::to_string(MAX_NAME_LENGTH); return false; } + std::vector parts; + boost::split(parts, name, boost::is_any_of(VOTE_TAG_DELIMITER)); + bool valid = IsNameValidBeforeTag(parts.front()) && IsVoteTagValid(parts.back()); + if (!valid) { error = "Vote name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can't be the first or last characters)"; return false; } + return true; + } else { + if (name.size() > MAX_NAME_LENGTH - 1) { error = "Name is greater than max length of " + std::to_string(MAX_NAME_LENGTH - 1); return false; } //Assets and sub-assets need to leave one extra char for OWNER indicator + if (!IsAssetNameASubasset(name) && name.size() < MIN_ASSET_LENGTH) { error = "Name must be contain " + std::to_string(MIN_ASSET_LENGTH) + " characters"; return false; } + bool valid = IsNameValidBeforeTag(name); + if (!valid && IsAssetNameASubasset(name) && name.size() < 3) { error = "Name must have at least 3 characters (Valid characters are: A-Z 0-9 _ .)"; return false; } + if (!valid) { error = "Name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can't be the first or last characters)"; return false; } + return true; + } +} + std::string GetParentName(const std::string& name) { AssetType type; @@ -203,60 +252,108 @@ std::string GetParentName(const std::string& name) return name; } +std::string GetUniqueAssetName(const std::string& parent, const std::string& tag) +{ + if (!IsRootNameValid(parent)) + return ""; + + if (!IsUniqueTagValid(tag)) + return ""; + + return parent + "#" + tag; +} + bool CNewAsset::IsNull() const { return strName == ""; } -bool CNewAsset::IsValid(std::string& strError, CAssetsCache& assetCache, bool fCheckMempool, bool fCheckDuplicateInputs) const +bool CNewAsset::IsValid(std::string& strError, CAssetsCache& assetCache, bool fCheckMempool, bool fCheckDuplicateInputs, bool fForceDuplicateCheck) const { strError = ""; // Check our current passets to see if the asset has been created yet if (fCheckDuplicateInputs) { - if (assetCache.CheckIfAssetExists(this->strName)) { - strError = std::string("Invalid parameter: asset_name '") + strName + std::string("' has already been used"); + if (assetCache.CheckIfAssetExists(this->strName, fForceDuplicateCheck)) { + strError = std::string(_("Invalid parameter: asset_name '")) + strName + std::string(_("' has already been used")); return false; } } if (fCheckMempool) { - for (const CTxMemPoolEntry &entry : mempool.mapTx) { - CTransaction tx = entry.GetTx(); - if (tx.IsNewAsset()) { - CNewAsset asset; - std::string address; - AssetFromTransaction(tx, asset, address); - if (asset.strName == strName) { - strError = "Asset with this name is already in the mempool"; - return false; - } - } + if (mempool.mapAssetToHash.count(strName)) { + strError = _("Asset with this name is already in the mempool"); + return false; } } - if (!IsAssetNameValid(std::string(strName))) - strError = "Invalid parameter: asset_name must only consist of valid characters and have a size between 3 and 30 characters. See help for more details."; + AssetType assetType; + if (!IsAssetNameValid(std::string(strName), assetType)) { + strError = _("Invalid parameter: asset_name must only consist of valid characters and have a size between 3 and 30 characters. See help for more details."); + return false; + } - if (IsAssetNameAnOwner(std::string(strName))) - strError = "Invalid parameters: asset_name can't have a '!' at the end of it. See help for more details."; + if (assetType == AssetType::UNIQUE) { + if (units != UNIQUE_ASSET_UNITS) { + strError = _("Invalid parameter: units must be ") + std::to_string(UNIQUE_ASSET_UNITS / COIN); + return false; + } + if (nAmount != UNIQUE_ASSET_AMOUNT) { + strError = _("Invalid parameter: amount must be ") + std::to_string(UNIQUE_ASSET_AMOUNT); + return false; + } + if (nReissuable != 0) { + strError = _("Invalid parameter: reissuable must be 0"); + return false; + } + } - if (nAmount <= 0) - strError = "Invalid parameter: asset amount can't be equal to or less than zero."; + if (IsAssetNameAnOwner(std::string(strName))) { + strError = _("Invalid parameters: asset_name can't have a '!' at the end of it. See help for more details."); + return false; + } + + if (nAmount <= 0) { + strError = _("Invalid parameter: asset amount can't be equal to or less than zero."); + return false; + } - if (units < 0 || units > 8) - strError = "Invalid parameter: units must be between 0-8."; + if (nAmount > MAX_MONEY) { + strError = _("Invalid parameter: asset amount greater than max money: ") + std::to_string(MAX_MONEY / COIN); + return false; + } - if (nReissuable != 0 && nReissuable != 1) - strError = "Invalid parameter: reissuable must be 0 or 1"; + if (units < 0 || units > 8) { + strError = _("Invalid parameter: units must be between 0-8."); + return false; + } - if (nHasIPFS != 0 && nHasIPFS != 1) - strError = "Invalid parameter: has_ipfs must be 0 or 1."; + if (!CheckAmountWithUnits(nAmount, units)) { + strError = _("Invalid parameter: amount must be divisible by the smaller unit assigned to the asset"); + return false; + } - if (nHasIPFS && strIPFSHash.size() != 34) - strError = "Invalid parameter: ipfs_hash must be 34 bytes."; + if (nReissuable != 0 && nReissuable != 1) { + strError = _("Invalid parameter: reissuable must be 0 or 1"); + return false; + } - return strError == ""; + if (nHasIPFS != 0 && nHasIPFS != 1) { + strError = _("Invalid parameter: has_ipfs must be 0 or 1."); + return false; + } + + if (nHasIPFS && strIPFSHash.size() != 34) { + strError = _("Invalid parameter: ipfs_hash must be 34 bytes."); + return false; + } + + if (nHasIPFS) { + if (!CheckEncodedIPFS(EncodeIPFS(strIPFSHash), strError)) + return false; + } + + return true; } CNewAsset::CNewAsset(const CNewAsset& asset) @@ -306,6 +403,29 @@ CNewAsset::CNewAsset(const std::string& strName, const CAmount& nAmount, const i this->nHasIPFS = int8_t(nHasIPFS); this->strIPFSHash = strIPFSHash; } +CNewAsset::CNewAsset(const std::string& strName, const CAmount& nAmount) +{ + this->SetNull(); + this->strName = strName; + this->nAmount = nAmount; + this->units = int8_t(DEFAULT_UNITS); + this->nReissuable = int8_t(DEFAULT_REISSUABLE); + this->nHasIPFS = int8_t(DEFAULT_HAS_IPFS); + this->strIPFSHash = DEFAULT_IPFS; +} + +CDatabasedAssetData::CDatabasedAssetData(const CNewAsset& asset, const int& nHeight, const uint256& blockHash) +{ + this->SetNull(); + this->asset = asset; + this->nHeight = nHeight; + this->blockHash = blockHash; +} + +CDatabasedAssetData::CDatabasedAssetData() +{ + this->SetNull(); +} /** * Constructs a CScript that carries the asset name and quantity and adds to to the end of the given script @@ -324,7 +444,7 @@ void CNewAsset::ConstructTransaction(CScript& script) const vchMessage.push_back(RVN_Q); // q vchMessage.insert(vchMessage.end(), ssAsset.begin(), ssAsset.end()); - script << OP_RVN_ASSET << vchMessage << OP_DROP; + script << OP_RVN_ASSET << ToByteVector(vchMessage) << OP_DROP; } void CNewAsset::ConstructOwnerTransaction(CScript& script) const @@ -339,7 +459,7 @@ void CNewAsset::ConstructOwnerTransaction(CScript& script) const vchMessage.push_back(RVN_O); // o vchMessage.insert(vchMessage.end(), ssOwner.begin(), ssOwner.end()); - script << OP_RVN_ASSET << vchMessage << OP_DROP; + script << OP_RVN_ASSET << ToByteVector(vchMessage) << OP_DROP; } bool AssetFromTransaction(const CTransaction& tx, CNewAsset& asset, std::string& strAddress) @@ -366,6 +486,18 @@ bool ReissueAssetFromTransaction(const CTransaction& tx, CReissueAsset& reissue, return ReissueAssetFromScript(scriptPubKey, reissue, strAddress); } +bool UniqueAssetFromTransaction(const CTransaction& tx, CNewAsset& asset, std::string& strAddress) +{ + // Check to see if the transaction is an new asset issue tx + if (!tx.IsNewUniqueAsset()) + return false; + + // Get the scriptPubKey from the last tx in vout + CScript scriptPubKey = tx.vout[tx.vout.size() - 1].scriptPubKey; + + return AssetFromScript(scriptPubKey, asset, strAddress); +} + bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg) { // TODO when ready to ship. Put the owner validation code in own method if needed @@ -508,12 +640,41 @@ bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, return true; } +//! Call VerifyNewAsset if this function returns true bool CTransaction::IsNewAsset() const { - // Issuing an Asset must contain at least 3 CTxOut( Raven Burn Tx, Any Number of other Outputs ..., Owner Asset Change Tx, Reissue Tx) + // Check for the assets data CTxOut. This will always be the last output in the transaction + if (!CheckIssueDataTx(vout[vout.size() - 1])) + return false; + + // Check to make sure the owner asset is created + if (!CheckOwnerDataTx(vout[vout.size() - 2])) + return false; + + // Don't overlap with IsNewUniqueAsset() + if (IsScriptNewUniqueAsset(vout[vout.size() - 1].scriptPubKey)) + return false; + + return true; +} + +//! To be called on CTransactions where IsNewAsset returns true +bool CTransaction::VerifyNewAsset() const +{ + // Issuing an Asset must contain at least 3 CTxOut( Raven Burn Tx, Any Number of other Outputs ..., Owner Asset Tx, New Asset Tx) if (vout.size() < 3) return false; + // Loop through all of the vouts and make sure only the expected asset creations are taking place + int nTransfers = 0; + int nOwners = 0; + int nIssues = 0; + int nReissues = 0; + GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); + + if (nOwners != 1 || nIssues != 1 || nReissues > 0) + return false; + // Check for the assets data CTxOut. This will always be the last output in the transaction if (!CheckIssueDataTx(vout[vout.size() - 1])) return false; @@ -531,6 +692,13 @@ bool CTransaction::IsNewAsset() const AssetType assetType; IsAssetNameValid(asset.strName, assetType); + std::string strOwnerName; + if (!OwnerAssetFromScript(vout[vout.size() - 2].scriptPubKey, strOwnerName, address)) + return false; + + if (strOwnerName != asset.strName + OWNER_TAG) + return false; + // Check for the Burn CTxOut in one of the vouts ( This is needed because the change CTxOut is places in a random position in the CWalletTx for (auto out : vout) if (CheckIssueBurnTx(out, assetType)) @@ -539,7 +707,120 @@ bool CTransaction::IsNewAsset() const return false; } +//! Make sure to call VerifyNewUniqueAsset if this call returns true +bool CTransaction::IsNewUniqueAsset() const +{ + // Check trailing outpoint for issue data with unique asset name + if (!CheckIssueDataTx(vout[vout.size() - 1])) + return false; + + if (!IsScriptNewUniqueAsset(vout[vout.size() - 1].scriptPubKey)) + return false; + + return true; +} + +//! Call this function after IsNewUniqueAsset +bool CTransaction::VerifyNewUniqueAsset(CCoinsViewCache& view) const +{ + // Must contain at least 3 outpoints (RVN burn, owner change and one or more new unique assets that share a root (should be in trailing position)) + if (vout.size() < 3) + return false; + + // check for (and count) new unique asset outpoints. make sure they share a root. + std::string assetRoot = ""; + int assetOutpointCount = 0; + for (auto out : vout) { + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + CNewAsset asset; + std::string address; + if (!AssetFromScript(out.scriptPubKey, asset, address)) + return false; + std::string root = GetParentName(asset.strName); + if (assetRoot.compare("") == 0) + assetRoot = root; + if (assetRoot.compare(root) != 0) + return false; + assetOutpointCount += 1; + } + } + if (assetOutpointCount == 0) + return false; + + // check for burn outpoint (must account for each new asset) + bool fBurnOutpointFound = false; + for (auto out : vout) + if (CheckIssueBurnTx(out, AssetType::UNIQUE, assetOutpointCount)) { + fBurnOutpointFound = true; + break; + } + if (!fBurnOutpointFound) + return false; + + // check for owner change outpoint that matches root + bool fOwnerOutFound = false; + for (auto out : vout) { + if (CheckTransferOwnerTx(out)) { + fOwnerOutFound = true; + break; + } + } + + if (!fOwnerOutFound) + return false; + + // The owner change output must match a corresponding owner input + bool fFoundCorrectInput = false; + for (unsigned int i = 0; i < vin.size(); ++i) { + const COutPoint &prevout = vin[i].prevout; + const Coin& coin = view.AccessCoin(prevout); + assert(!coin.IsSpent()); + + int nType = -1; + bool fOwner = false; + if (coin.out.scriptPubKey.IsAssetScript(nType, fOwner)) { + std::string strAssetName; + CAmount nAssetAmount; + if (!GetAssetInfoFromCoin(coin, strAssetName, nAssetAmount)) + continue; + if (IsAssetNameAnOwner(strAssetName)) { + if (strAssetName == assetRoot + OWNER_TAG) { + fFoundCorrectInput = true; + break; + } + } + } + } + + if (!fFoundCorrectInput) + return false; + + // Loop through all of the vouts and make sure only the expected asset creations are taking place + int nTransfers = 0; + int nOwners = 0; + int nIssues = 0; + int nReissues = 0; + GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); + + if (nOwners > 0 || nReissues > 0 || nIssues != assetOutpointCount) { + return false; + } + + + return true; +} + bool CTransaction::IsReissueAsset() const +{ + // Check for the reissue asset data CTxOut. This will always be the last output in the transaction + if (!CheckReissueDataTx(vout[vout.size() - 1])) + return false; + + return true; +} + +//! To be called on CTransactions where IsReissueAsset returns true +bool CTransaction::VerifyReissueAsset(CCoinsViewCache& view) const { // Reissuing an Asset must contain at least 3 CTxOut ( Raven Burn Tx, Any Number of other Outputs ..., Reissue Asset Tx, Owner Asset Change Tx) if (vout.size() < 3) @@ -550,14 +831,45 @@ bool CTransaction::IsReissueAsset() const return false; // Check that there is an asset transfer, this will be the owner asset change - bool ownerFound = false; - for (auto out : vout) + bool fOwnerOutFound = false; + for (auto out : vout) { if (CheckTransferOwnerTx(out)) { - ownerFound = true; + fOwnerOutFound = true; break; } + } + + if (!fOwnerOutFound) + return false; + + CReissueAsset reissue; + std::string address; + if (!ReissueAssetFromScript(vout[vout.size() - 1].scriptPubKey, reissue, address)) + return false; + + bool fFoundCorrectInput = false; + for (unsigned int i = 0; i < vin.size(); ++i) { + const COutPoint &prevout = vin[i].prevout; + const Coin& coin = view.AccessCoin(prevout); + assert(!coin.IsSpent()); + + int nType = -1; + bool fOwner = false; + if (coin.out.scriptPubKey.IsAssetScript(nType, fOwner)) { + std::string strAssetName; + CAmount nAssetAmount; + if (!GetAssetInfoFromCoin(coin, strAssetName, nAssetAmount)) + continue; + if (IsAssetNameAnOwner(strAssetName)) { + if (strAssetName == reissue.strName + OWNER_TAG) { + fFoundCorrectInput = true; + break; + } + } + } + } - if (!ownerFound) + if (!fFoundCorrectInput) return false; // Check for the Burn CTxOut in one of the vouts ( This is needed because the change CTxOut is placed in a random position in the CWalletTx @@ -565,6 +877,17 @@ bool CTransaction::IsReissueAsset() const if (CheckReissueBurnTx(out)) return true; + + // Loop through all of the vouts and make sure only the expected asset creations are taking place + int nTransfers = 0; + int nOwners = 0; + int nIssues = 0; + int nReissues = 0; + GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); + + if (nOwners > 0 || nReissues != 1 || nIssues > 0) + return false; + return false; } @@ -599,10 +922,10 @@ void CAssetTransfer::ConstructTransaction(CScript& script) const vchMessage.push_back(RVN_T); // t vchMessage.insert(vchMessage.end(), ssTransfer.begin(), ssTransfer.end()); - script << OP_RVN_ASSET << vchMessage << OP_DROP; + script << OP_RVN_ASSET << ToByteVector(vchMessage) << OP_DROP; } -CReissueAsset::CReissueAsset(const std::string &strAssetName, const CAmount &nAmount, const int &nReissuable, +CReissueAsset::CReissueAsset(const std::string &strAssetName, const CAmount &nAmount, const int &nUnits, const int &nReissuable, const std::string &strIPFSHash) { @@ -610,6 +933,7 @@ CReissueAsset::CReissueAsset(const std::string &strAssetName, const CAmount &nAm this->strIPFSHash = strIPFSHash; this->nReissuable = int8_t(nReissuable); this->nAmount = nAmount; + this->nUnits = nUnits; } bool CReissueAsset::IsValid(std::string &strError, CAssetsCache& assetCache) const @@ -617,35 +941,50 @@ bool CReissueAsset::IsValid(std::string &strError, CAssetsCache& assetCache) con strError = ""; CNewAsset asset; - if (!assetCache.GetAssetIfExists(this->strName, asset)) { - strError = std::string("Unable to reissue asset: asset_name '") + strName + std::string("' doesn't exist in the database"); + if (!assetCache.GetAssetMetaDataIfExists(this->strName, asset)) { + strError = _("Unable to reissue asset: asset_name '") + strName + _("' doesn't exist in the database"); return false; } if (!asset.nReissuable) { // Check to make sure the asset can be reissued - strError = "Unable to reissue asset: reissuable is set to false"; + strError = _("Unable to reissue asset: reissuable is set to false"); return false; } if (asset.nAmount + this->nAmount > MAX_MONEY) { - strError = std::string("Unable to reissue asset: asset_name '") + strName + - std::string("' the amount trying to reissue is to large"); + strError = _("Unable to reissue asset: asset_name '") + strName + + _("' the amount trying to reissue is to large"); return false; } - if (nAmount % int64_t(pow(10, (MAX_UNIT - asset.units))) != 0) { - strError = "Unable to reissue asset: amount must be divisable by the smaller unit assigned to the asset"; + if (!CheckAmountWithUnits(nAmount, asset.units)) { + strError = _("Unable to reissue asset: amount must be divisible by the smaller unit assigned to the asset"); return false; } if (strIPFSHash != "" && strIPFSHash.size() != 34) { - strError = "Unable to reissue asset: new ipfs_hash must be 34 bytes."; + strError = _("Invalid parameter: ipfs_hash must be 34 bytes."); return false; } - if (nAmount <= 0) { - strError = "Unable to reissue asset: amount must be 1 or larger"; + if (strIPFSHash != "") { + if (!CheckEncodedIPFS(EncodeIPFS(strIPFSHash), strError)) + return false; + } + + if (nAmount < 0) { + strError = _("Unable to reissue asset: amount must be 0 or larger"); + return false; + } + + if (nUnits > MAX_UNIT || nUnits < -1) { + strError = _("Unable to reissue asset: unit must be less than 8 and greater than -1"); + return false; + } + + if (nUnits < asset.units && nUnits != -1) { + strError = _("Unable to reissue asset: unit must be larger than current unit selection"); return false; } @@ -664,12 +1003,12 @@ void CReissueAsset::ConstructTransaction(CScript& script) const vchMessage.push_back(RVN_R); // r vchMessage.insert(vchMessage.end(), ssReissue.begin(), ssReissue.end()); - script << OP_RVN_ASSET << vchMessage << OP_DROP; + script << OP_RVN_ASSET << ToByteVector(vchMessage) << OP_DROP; } bool CReissueAsset::IsNull() const { - return strName == "" || nAmount == 0; + return strName == "" || nAmount < 0; } bool CAssetsCache::GetAssetsOutPoints(const std::string& strName, std::set& outpoints) @@ -737,30 +1076,29 @@ bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) CAmount nAmount = -1; // Get the asset tx data - int nType = 0; + int nType = -1; bool fIsOwner = false; if (txOut.scriptPubKey.IsAssetScript(nType, fIsOwner)) { - txnouttype type = (txnouttype)nType; // Get the New Asset or Transfer Asset from the scriptPubKey - if (type == TX_NEW_ASSET && !fIsOwner) { + if (nType == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; if (AssetFromScript(txOut.scriptPubKey, asset, address)) { assetName = asset.strName; nAmount = asset.nAmount; } - } else if (type == TX_TRANSFER_ASSET) { + } else if (nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; if (TransferAssetFromScript(txOut.scriptPubKey, transfer, address)) { assetName = transfer.strName; nAmount = transfer.nAmount; } - } else if (type == TX_NEW_ASSET && fIsOwner) { + } else if (nType == TX_NEW_ASSET && fIsOwner) { if (!OwnerAssetFromScript(txOut.scriptPubKey, assetName, address)) return error("%s : ERROR Failed to get owner asset from the OutPoint: %s", __func__, out.ToString()); nAmount = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { + } else if (nType == TX_REISSUE_ASSET) { CReissueAsset reissue; if (ReissueAssetFromScript(txOut.scriptPubKey, reissue, address)) { assetName = reissue.strName; @@ -776,19 +1114,18 @@ bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) if (address != "" && assetName != "" && nAmount > 0) { CAssetCacheSpendAsset spend(assetName, address, nAmount); if (GetBestAssetAddressAmount(*this, assetName, address)) { - assert(mapAssetsAddressAmount[make_pair(assetName, address)] >= nAmount); - mapAssetsAddressAmount[make_pair(assetName, address)] -= nAmount; + auto pair = make_pair(assetName, address); + mapAssetsAddressAmount.at(pair) -= nAmount; - if (mapAssetsAddressAmount[make_pair(assetName, address)] == 0 && + if (mapAssetsAddressAmount.at(pair) < 0) + mapAssetsAddressAmount.at(pair) = 0; + if (mapAssetsAddressAmount.at(pair) == 0 && mapAssetsAddresses.count(assetName)) mapAssetsAddresses.at(assetName).erase(address); // Update the cache so we can save to database vSpentAssets.push_back(spend); - } else { - return error("%s : ERROR Failed to find current assets address amount. Asset %s: , Address : %s", __func__, assetName, address); } - } else { return error("%s : ERROR Failed to get asset from the OutPoint: %s", __func__, out.ToString()); } @@ -856,13 +1193,11 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) CAmount nAmount = 0; // Get the asset tx from the script - txnouttype type; - int nType = 0; + int nType = -1; bool fIsOwner = false; if(coin.out.scriptPubKey.IsAssetScript(nType, fIsOwner)) { - type = (txnouttype) nType; - if (type == TX_NEW_ASSET && !fIsOwner) { + if (nType == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; if (!AssetFromScript(coin.out.scriptPubKey, asset, strAddress)) { return error("%s : Failed to get asset from script while trying to undo asset spend. OutPoint : %s", @@ -872,7 +1207,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) assetName = asset.strName; nAmount = asset.nAmount; - } else if (type == TX_TRANSFER_ASSET) { + } else if (nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; if (!TransferAssetFromScript(coin.out.scriptPubKey, transfer, strAddress)) return error( @@ -882,7 +1217,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) assetName = transfer.strName; nAmount = transfer.nAmount; - } else if (type == TX_NEW_ASSET && fIsOwner) { + } else if (nType == TX_NEW_ASSET && fIsOwner) { std::string ownerName; if (!OwnerAssetFromScript(coin.out.scriptPubKey, ownerName, strAddress)) return error( @@ -890,7 +1225,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) __func__, out.ToString()); assetName = ownerName; nAmount = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { + } else if (nType == TX_REISSUE_ASSET) { CReissueAsset reissue; if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, strAddress)) return error( @@ -994,7 +1329,7 @@ bool CAssetsCache::RemoveNewAsset(const CNewAsset& asset, const std::string addr mapAssetsAddressAmount[std::make_pair(asset.strName, address)] = 0; - CAssetCacheNewAsset newAsset(asset, address); + CAssetCacheNewAsset newAsset(asset, address, 0 , uint256()); if (setNewAssetsToAdd.count(newAsset)) setNewAssetsToAdd.erase(newAsset); @@ -1005,7 +1340,7 @@ bool CAssetsCache::RemoveNewAsset(const CNewAsset& asset, const std::string addr } //! Changes Memory Only -bool CAssetsCache::AddNewAsset(const CNewAsset& asset, const std::string address) +bool CAssetsCache::AddNewAsset(const CNewAsset& asset, const std::string address, const int& nHeight, const uint256& blockHash) { if(CheckIfAssetExists(asset.strName)) return error("%s: Tried adding new asset, but it already existed in the set of assets: %s", __func__, asset.strName); @@ -1025,7 +1360,7 @@ bool CAssetsCache::AddNewAsset(const CNewAsset& asset, const std::string address // Insert the asset into the assests address amount map mapAssetsAddressAmount[std::make_pair(asset.strName, address)] = asset.nAmount; - CAssetCacheNewAsset newAsset(asset, address); + CAssetCacheNewAsset newAsset(asset, address, nHeight, blockHash); if (setNewAssetsToRemove.count(newAsset)) setNewAssetsToRemove.erase(newAsset); @@ -1040,9 +1375,12 @@ bool CAssetsCache::AddReissueAsset(const CReissueAsset& reissue, const std::stri { auto pair = std::make_pair(reissue.strName, address); - CNewAsset assetData; - if (!GetAssetIfExists(reissue.strName, assetData)) - return error("%s: Tried reissuing an asset, but that asset didn't exist: %s", __func__, reissue.strName); + CNewAsset asset; + int assetHeight; + uint256 assetBlockHash; + if (!GetAssetMetaDataIfExists(reissue.strName, asset, assetHeight, assetBlockHash)) + return error("%s: Failed to get the original asset that is getting reissued. Asset Name : %s", + __func__, reissue.strName); // Insert the asset into the assets address map if (mapAssetsAddresses.count(reissue.strName)) { @@ -1063,13 +1401,16 @@ bool CAssetsCache::AddReissueAsset(const CReissueAsset& reissue, const std::stri // Insert the reissue information into the reissue map if (!mapReissuedAssetData.count(reissue.strName)) { - assetData.nAmount += reissue.nAmount; - assetData.nReissuable = reissue.nReissuable; + asset.nAmount += reissue.nAmount; + asset.nReissuable = reissue.nReissuable; + if (reissue.nUnits != -1) + asset.units = reissue.nUnits; + if (reissue.strIPFSHash != "") { - assetData.nHasIPFS = 1; - assetData.strIPFSHash = reissue.strIPFSHash; + asset.nHasIPFS = 1; + asset.strIPFSHash = reissue.strIPFSHash; } - mapReissuedAssetData.insert(make_pair(reissue.strName, assetData)); + mapReissuedAssetData.insert(make_pair(reissue.strName, asset)); } else { mapReissuedAssetData.at(reissue.strName).nAmount += reissue.nAmount; mapReissuedAssetData.at(reissue.strName).nReissuable = reissue.nReissuable; @@ -1079,7 +1420,7 @@ bool CAssetsCache::AddReissueAsset(const CReissueAsset& reissue, const std::stri } } - CAssetCacheReissueAsset reissueAsset(reissue, address, out); + CAssetCacheReissueAsset reissueAsset(reissue, address, out, assetHeight, assetBlockHash); if (setNewReissueToRemove.count(reissueAsset)) setNewReissueToRemove.erase(reissueAsset); @@ -1090,12 +1431,14 @@ bool CAssetsCache::AddReissueAsset(const CReissueAsset& reissue, const std::stri } //! Changes Memory Only -bool CAssetsCache::RemoveReissueAsset(const CReissueAsset& reissue, const std::string address, const COutPoint& out, const std::vector >& vUndoIPFS) +bool CAssetsCache::RemoveReissueAsset(const CReissueAsset& reissue, const std::string address, const COutPoint& out, const std::vector >& vUndoIPFS) { auto pair = std::make_pair(reissue.strName, address); CNewAsset assetData; - if (!GetAssetIfExists(reissue.strName, assetData)) + int height; + uint256 blockHash; + if (!GetAssetMetaDataIfExists(reissue.strName, assetData, height, blockHash)) return error("%s: Tried undoing reissue of an asset, but that asset didn't exist: %s", __func__, reissue.strName); // Remove the reissued asset outpoint if it belongs to my unspent assets @@ -1125,10 +1468,12 @@ bool CAssetsCache::RemoveReissueAsset(const CReissueAsset& reissue, const std::s assetData.nReissuable = 1; // Find the ipfs hash in the undoblock data and restore the ipfs hash to its previous hash - for (auto undoIPFS : vUndoIPFS) { - if (undoIPFS.first == reissue.strName) { - assetData.strIPFSHash = undoIPFS.second; - + for (auto undoItem : vUndoIPFS) { + if (undoItem.first == reissue.strName) { + if (undoItem.second.fChangedIPFS) + assetData.strIPFSHash = undoItem.second.strIPFS; + if(undoItem.second.fChangedUnits) + assetData.units = undoItem.second.nUnits; if (assetData.strIPFSHash == "") assetData.nHasIPFS = 0; break; @@ -1137,7 +1482,7 @@ bool CAssetsCache::RemoveReissueAsset(const CReissueAsset& reissue, const std::s mapReissuedAssetData[assetData.strName] = assetData; - CAssetCacheReissueAsset reissueAsset(reissue, address, out); + CAssetCacheReissueAsset reissueAsset(reissue, address, out, height, blockHash); if (setNewReissueToAdd.count(reissueAsset)) setNewReissueToAdd.erase(reissueAsset); @@ -1226,23 +1571,6 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) bool dirty = false; std::string message; - // Save the assets that have been spent by erasing the quantity in the database - for (auto spentAsset : vSpentAssets) { - auto pair = make_pair(spentAsset.assetName, spentAsset.address); - if (mapAssetsAddressAmount.count(pair)) { - if (mapAssetsAddressAmount.at(make_pair(spentAsset.assetName, spentAsset.address)) == 0) { - if (!passetsdb->EraseAssetAddressQuantity(spentAsset.assetName, spentAsset.address)) { - dirty = true; - message = "_Failed Erasing a Spent Asset, from database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } - } - } - } - // Remove new assets from the database for (auto newAsset : setNewAssetsToRemove) { passetsCache->Erase(newAsset.asset.strName); @@ -1268,8 +1596,8 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) // Add the new assets to the database for (auto newAsset : setNewAssetsToAdd) { - passetsCache->Put(newAsset.asset.strName, newAsset.asset); - if (!passetsdb->WriteAssetData(newAsset.asset)) { + passetsCache->Put(newAsset.asset.strName, CDatabasedAssetData(newAsset.asset, newAsset.blockHeight, newAsset.blockHash)); + if (!passetsdb->WriteAssetData(newAsset.asset, newAsset.blockHeight, newAsset.blockHash)) { dirty = true; message = "_Failed Writing New Asset Data to database"; } @@ -1366,7 +1694,7 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) auto reissue_name = newReissue.reissue.strName; auto pair = make_pair(reissue_name, newReissue.address); if (mapReissuedAssetData.count(reissue_name)) { - if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name))) { + if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name), newReissue.blockHeight, newReissue.blockHash)) { dirty = true; message = "_Failed Writing reissue asset data to database"; } @@ -1379,7 +1707,7 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) if (mapAssetsAddressAmount.count(pair)) { if (!passetsdb->WriteAssetAddressQuantity(pair.first, pair.second, - mapAssetsAddressAmount[pair])) { + mapAssetsAddressAmount.at(pair))) { dirty = true; message = "_Failed Writing reissue asset quantity to the address quantity database"; } @@ -1392,9 +1720,18 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) } for (auto undoReissue : setNewReissueToRemove) { + // In the case the the issue and reissue are both being removed + // we can skip this call because the removal of the issue should remove all data pertaining the to asset + // Fixes the issue where the reissue data will write over the removed asset meta data that was removed above + CNewAsset asset(undoReissue.reissue.strName, 0); + CAssetCacheNewAsset testNewAssetCache(asset, "", 0 , uint256()); + if (setNewAssetsToRemove.count(testNewAssetCache)) { + continue; + } + auto reissue_name = undoReissue.reissue.strName; if (mapReissuedAssetData.count(reissue_name)) { - if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name))) { + if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name), undoReissue.blockHeight, undoReissue.blockHash)) { dirty = true; message = "_Failed Writing undo reissue asset data to database"; } @@ -1452,6 +1789,32 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) } } + // Save the assets that have been spent by erasing the quantity in the database + for (auto spentAsset : vSpentAssets) { + auto pair = make_pair(spentAsset.assetName, spentAsset.address); + if (mapAssetsAddressAmount.count(pair)) { + if (mapAssetsAddressAmount.at(make_pair(spentAsset.assetName, spentAsset.address)) == 0) { + if (!passetsdb->EraseAssetAddressQuantity(spentAsset.assetName, spentAsset.address)) { + dirty = true; + message = "_Failed Erasing a Spent Asset, from database"; + } + + if (dirty) { + return error("%s : %s", __func__, message); + } + } else { + if (!passetsdb->WriteAssetAddressQuantity(spentAsset.assetName, spentAsset.address, mapAssetsAddressAmount.at(pair))) { + dirty = true; + message = "_Failed Erasing a Spent Asset, from database"; + } + + if (dirty) { + return error("%s : %s", __func__, message); + } + } + } + } + ClearDirtyCache(); } @@ -1497,7 +1860,7 @@ bool IsAssetUnitsValid(const CAmount& units) return false; } -bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued) { CAmount burnAmount = 0; std::string burnAddress = ""; @@ -1515,6 +1878,9 @@ bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) return false; } + // If issuing multiple (unique) assets need to burn for each + burnAmount *= numberIssued; + // Check the first transaction for the required Burn Amount for the asset type if (!(txOut.nValue == burnAmount)) return false; @@ -1536,6 +1902,11 @@ bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) return true; } +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) +{ + return CheckIssueBurnTx(txOut, type, 1); +} + bool CheckReissueBurnTx(const CTxOut& txOut) { // Check the first transaction and verify that the correct RVN Amount @@ -1602,12 +1973,36 @@ bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_NEW_ASSET && !fIsOwner; + return nType == TX_NEW_ASSET && !fIsOwner; } - return false; } +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey) +{ + int index = 0; + return IsScriptNewUniqueAsset(scriptPubKey, index); +} + +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner = false; + if (!scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) + return false; + + CNewAsset asset; + std::string address; + if (!AssetFromScript(scriptPubKey, asset, address)) + return false; + + AssetType assetType; + if (!IsAssetNameValid(asset.strName, assetType)) + return false; + + return AssetType::UNIQUE == assetType; +} + bool IsScriptOwnerAsset(const CScript& scriptPubKey) { @@ -1620,7 +2015,7 @@ bool IsScriptOwnerAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_NEW_ASSET && fIsOwner; + return nType == TX_NEW_ASSET && fIsOwner; } return false; @@ -1637,7 +2032,7 @@ bool IsScriptReissueAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_REISSUE_ASSET; + return nType == TX_REISSUE_ASSET; } return false; @@ -1654,7 +2049,7 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_TRANSFER_ASSET; + return nType == TX_TRANSFER_ASSET; } return false; @@ -1698,32 +2093,47 @@ void UpdatePossibleAssets() //! Returns a boolean on if the asset exists -bool CAssetsCache::CheckIfAssetExists(const std::string& name) +bool CAssetsCache::CheckIfAssetExists(const std::string& name, bool fForceDuplicateCheck) { // TODO we need to add some Locks to this I would think // Create objects that will be used to check the dirty cache CNewAsset asset; asset.strName = name; - CAssetCacheNewAsset cachedAsset(asset, ""); + CAssetCacheNewAsset cachedAsset(asset, "", 0, uint256()); // Check the dirty caches first and see if it was recently added or removed if (setNewAssetsToRemove.count(cachedAsset)) return false; - if (setNewAssetsToAdd.count(cachedAsset)) - return true; + if (setNewAssetsToAdd.count(cachedAsset)) { + if (fForceDuplicateCheck) + return true; + else { + LogPrintf("%s : Found asset %s in setNewAssetsToAdd but force duplicate check wasn't true\n", __func__, name); + } + } // Check the cache, if it doesn't exist in the cache. Try and read it from database if (passetsCache) { if (passetsCache->Exists(name)) { - return true; + if (fForceDuplicateCheck) + return true; + else { + LogPrintf("%s : Found asset %s in passetsCache but force duplicate check wasn't true\n", __func__, name); + } } else { if (passetsdb) { CNewAsset readAsset; - if (passetsdb->ReadAssetData(name, readAsset)) { - passetsCache->Put(readAsset.strName, readAsset); - return true; + int nHeight; + uint256 hash; + if (passetsdb->ReadAssetData(name, readAsset, nHeight, hash)) { + passetsCache->Put(readAsset.strName, CDatabasedAssetData(readAsset, nHeight, hash)); + if (fForceDuplicateCheck) + return true; + else { + LogPrintf("%s : Found asset %s in passetsdb but force duplicate check wasn't true\n", __func__, name); + } } } } @@ -1732,7 +2142,14 @@ bool CAssetsCache::CheckIfAssetExists(const std::string& name) return false; } -bool CAssetsCache::GetAssetIfExists(const std::string& name, CNewAsset& asset) +bool CAssetsCache::GetAssetMetaDataIfExists(const std::string &name, CNewAsset &asset) +{ + int height; + uint256 hash; + return GetAssetMetaDataIfExists(name, asset, height, hash); +} + +bool CAssetsCache::GetAssetMetaDataIfExists(const std::string &name, CNewAsset &asset, int& nHeight, uint256& blockHash) { // Check the map that contains the reissued asset data. If it is in this map, it hasn't been saved to disk yet if (mapReissuedAssetData.count(name)) { @@ -1743,7 +2160,7 @@ bool CAssetsCache::GetAssetIfExists(const std::string& name, CNewAsset& asset) // Create objects that will be used to check the dirty cache CNewAsset tempAsset; tempAsset.strName = name; - CAssetCacheNewAsset cachedAsset(tempAsset, ""); + CAssetCacheNewAsset cachedAsset(tempAsset, "", 0, uint256()); // Check the dirty caches first and see if it was recently added or removed if (setNewAssetsToRemove.count(cachedAsset)) { @@ -1753,22 +2170,32 @@ bool CAssetsCache::GetAssetIfExists(const std::string& name, CNewAsset& asset) auto setIterator = setNewAssetsToAdd.find(cachedAsset); if (setIterator != setNewAssetsToAdd.end()) { asset = setIterator->asset; + nHeight = setIterator->blockHeight; + blockHash = setIterator->blockHash; return true; } // Check the cache, if it doesn't exist in the cache. Try and read it from database if (passetsCache) { if (passetsCache->Exists(name)) { - asset = passetsCache->Get(name); + CDatabasedAssetData data; + data = passetsCache->Get(name); + asset = data.asset; + nHeight = data.nHeight; + blockHash = data.blockHash; return true; } } if (passetsdb && passetsCache) { CNewAsset readAsset; - if (passetsdb->ReadAssetData(name, readAsset)) { + int height; + uint256 hash; + if (passetsdb->ReadAssetData(name, readAsset, height, hash)) { asset = readAsset; - passetsCache->Put(readAsset.strName, readAsset); + nHeight = height; + blockHash = hash; + passetsCache->Put(readAsset.strName, CDatabasedAssetData(readAsset, height, hash)); return true; } } @@ -1776,65 +2203,33 @@ bool CAssetsCache::GetAssetIfExists(const std::string& name, CNewAsset& asset) return false; } -bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) +bool GetAssetInfoFromScript(const CScript& scriptPubKey, std::string& strName, CAmount& nAmount) { - int nType = 0; - bool fIsOwner = false; - if (!coin.out.scriptPubKey.IsAssetScript(nType, fIsOwner)) { + CAssetOutputEntry data; + if(!GetAssetData(scriptPubKey, data)) return false; - } - txnouttype type = txnouttype(nType); + strName = data.assetName; + nAmount = data.nAmount; - // Determine the type of asset that the scriptpubkey contains and return the name and amount - if (type == TX_NEW_ASSET && !fIsOwner) { - CNewAsset asset; - std::string address; - if (!AssetFromScript(coin.out.scriptPubKey, asset, address)) - return false; - strName = asset.strName; - nAmount = asset.nAmount; - return true; - } else if (type == TX_TRANSFER_ASSET) { - CAssetTransfer asset; - std::string address; - if (!TransferAssetFromScript(coin.out.scriptPubKey, asset, address)) - return false; - strName = asset.strName; - nAmount = asset.nAmount; - return true; - } else if (type == TX_NEW_ASSET && fIsOwner) { - std::string name; - std::string address; - if (!OwnerAssetFromScript(coin.out.scriptPubKey, name, address)) - return false; - strName = name; - nAmount = OWNER_ASSET_AMOUNT; - return true; - } else if (type == TX_REISSUE_ASSET) { - CReissueAsset reissue; - std::string address; - if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, address)) - return false; - strName = reissue.strName; - nAmount = reissue.nAmount; - return true; - } + return true; +} - return false; +bool GetAssetInfoFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) +{ + return GetAssetInfoFromScript(coin.out.scriptPubKey, strName, nAmount); } -void GetAssetData(const CScript& script, CAssetOutputEntry& data) +bool GetAssetData(const CScript& script, CAssetOutputEntry& data) { // Placeholder strings that will get set if you successfully get the transfer or asset from the script std::string address = ""; std::string assetName = ""; - int nType = 0; bool fIsOwner = false; if (!script.IsAssetScript(nType, fIsOwner)) { - return; + return false; } txnouttype type = txnouttype(nType); @@ -1843,63 +2238,81 @@ void GetAssetData(const CScript& script, CAssetOutputEntry& data) if (type == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; if (AssetFromScript(script, asset, address)) { - assetName = asset.strName; - data.type = ASSET_NEW_STRING; - data.amount = asset.nAmount; + data.type = TX_NEW_ASSET; + data.nAmount = asset.nAmount; data.destination = DecodeDestination(address); data.assetName = asset.strName; + return true; } } else if (type == TX_TRANSFER_ASSET) { CAssetTransfer transfer; if (TransferAssetFromScript(script, transfer, address)) { - assetName = transfer.strName; - data.type = ASSET_TRANSFER_STRING; - data.amount = transfer.nAmount; + data.type = TX_TRANSFER_ASSET; + data.nAmount = transfer.nAmount; data.destination = DecodeDestination(address); data.assetName = transfer.strName; + return true; } - } else if (type == TX_NEW_ASSET && !fIsOwner) { + } else if (type == TX_NEW_ASSET && fIsOwner) { if (OwnerAssetFromScript(script, assetName, address)) { - data.type = ASSET_NEW_STRING; - data.amount = OWNER_ASSET_AMOUNT; + data.type = TX_NEW_ASSET; + data.nAmount = OWNER_ASSET_AMOUNT; data.destination = DecodeDestination(address); data.assetName = assetName; + return true; } } else if (type == TX_REISSUE_ASSET) { CReissueAsset reissue; if (ReissueAssetFromScript(script, reissue, address)) { - assetName = reissue.strName; - data.type = ASSET_REISSUE_STRING; - data.amount = reissue.nAmount; + data.type = TX_REISSUE_ASSET; + data.nAmount = reissue.nAmount; data.destination = DecodeDestination(address); data.assetName = reissue.strName; + return true; } } + + return false; } -bool CheckAssetOwner(const std::string& assetName) +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names, int nMinConf) { - if (passets->mapMyUnspentAssets.count(assetName + OWNER_TAG)) { - return true; - } + if(!pwallet) + return; - return false; + GetAllMyAssets(pwallet, names, nMinConf, true, true); } -void GetAllOwnedAssets(std::vector& names) +void GetAllMyAssets(CWallet* pwallet, std::vector& names, int nMinConf, bool fIncludeAdministrator, bool fOnlyAdministrator) { - for (auto owned : passets->mapMyUnspentAssets) { - if (IsAssetNameAnOwner(owned.first)) { - names.emplace_back(owned.first); + if(!pwallet) + return; + + std::map > mapAssets; + pwallet->AvailableAssets(mapAssets, true, nullptr, 1, MAX_MONEY, MAX_MONEY, 0, nMinConf); // Set the mincof, set the rest to the defaults + + for (auto item : mapAssets) { + bool isOwner = IsAssetNameAnOwner(item.first); + + if (isOwner) { + if (fOnlyAdministrator || fIncludeAdministrator) + names.emplace_back(item.first); + } else { + if (fOnlyAdministrator) + continue; + names.emplace_back(item.first); } } } -void GetAllMyAssets(std::vector& names) +void GetAllMyAssetsFromCache(std::vector& names) { - for (auto owned : passets->mapMyUnspentAssets) { + if (!passets) + return; + + for (auto owned : passets->mapMyUnspentAssets) names.emplace_back(owned.first); - } + } CAmount GetIssueAssetBurnAmount() @@ -1922,6 +2335,11 @@ CAmount GetIssueUniqueAssetBurnAmount() return Params().IssueUniqueAssetBurnAmount(); } +CAmount GetBurnAmount(const int nType) +{ + return GetBurnAmount((AssetType(nType))); +} + CAmount GetBurnAmount(const AssetType type) { switch (type) { @@ -1944,6 +2362,11 @@ CAmount GetBurnAmount(const AssetType type) } } +std::string GetBurnAddress(const int nType) +{ + return GetBurnAddress((AssetType(nType))); +} + std::string GetBurnAddress(const AssetType type) { switch (type) { @@ -2068,16 +2491,24 @@ std::string EncodeIPFS(std::string decoded){ return EncodeBase58(unsignedCharData); }; -bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +bool CreateAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const CNewAsset& asset, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { + std::vector assets; + assets.push_back(asset); + return CreateAssetTransaction(pwallet, coinControl, assets, address, error, wtxNew, reservekey, nFeeRequired); +} - std::string change_address = rvnChangeAddress; +bool CreateAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const std::vector assets, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +{ + std::string change_address = EncodeDestination(coinControl.destChange); // Validate the assets data std::string strError; - if (!asset.IsValid(strError, *passets)) { - error = std::make_pair(RPC_INVALID_PARAMETER, strError); - return false; + for (auto asset : assets) { + if (!asset.IsValid(strError, *passets)) { + error = std::make_pair(RPC_INVALID_PARAMETER, strError); + return false; + } } if (!change_address.empty()) { @@ -2087,35 +2518,40 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: return false; } } else { - // Create a new address - std::string strAccount; - - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - - // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwallet->GetKeyFromPool(newKey)) { - error = std::make_pair(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + // no coin control: send change to newly generated address + CKeyID keyID; + std::string strFailReason; + if (!pwallet->CreateNewChangeAddress(reservekey, keyID, strFailReason)) { + error = std::make_pair(RPC_WALLET_KEYPOOL_RAN_OUT, strFailReason); return false; } - CKeyID keyID = newKey.GetID(); - - pwallet->SetAddressBook(keyID, strAccount, "receive"); change_address = EncodeDestination(keyID); + coinControl.destChange = DecodeDestination(change_address); } - AssetType assetType; - if (!IsAssetNameValid(asset.strName, assetType)) { - error = std::make_pair(RPC_INVALID_PARAMETER, "Asset name not valid"); - return false; + std::string parentName; + for (auto asset : assets) { + if (!IsAssetNameValid(asset.strName, assetType)) { + error = std::make_pair(RPC_INVALID_PARAMETER, "Asset name not valid"); + return false; + } + if (assets.size() > 1 && assetType != AssetType::UNIQUE) { + error = std::make_pair(RPC_INVALID_PARAMETER, "Only unique assets can be issued in bulk."); + return false; + } + std::string parent = GetParentName(asset.strName); + if (parentName.empty()) + parentName = parent; + if (parentName != parent) { + error = std::make_pair(RPC_INVALID_PARAMETER, "All assets must have the same parent."); + return false; + } } // Assign the correct burn amount and the correct burn address depending on the type of asset issuance that is happening - CAmount burnAmount = GetBurnAmount(assetType); + CAmount burnAmount = GetBurnAmount(assetType) * assets.size(); CScript scriptPubKey = GetScriptForDestination(DecodeDestination(GetBurnAddress(assetType))); CAmount curBalance = pwallet->GetBalance(); @@ -2131,11 +2567,6 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: return false; } - CCoinControl coin_control; - - coin_control.destChange = DecodeDestination(change_address); - - LOCK2(cs_main, pwallet->cs_wallet); // Create and send the transaction @@ -2147,28 +2578,28 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: CRecipient recipient = {scriptPubKey, burnAmount, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - // If the asset is a subasset. We need to send the ownertoken change back to ourselfs - if (assetType == AssetType::SUB) { + // If the asset is a subasset or unique asset. We need to send the ownertoken change back to ourselfs + if (assetType == AssetType::SUB || assetType == AssetType::UNIQUE) { // Get the script for the destination address for the assets CScript scriptTransferOwnerAsset = GetScriptForDestination(DecodeDestination(change_address)); - std::string parent_name = GetParentName(asset.strName); - CAssetTransfer assetTransfer(parent_name + OWNER_TAG, OWNER_ASSET_AMOUNT); + CAssetTransfer assetTransfer(parentName + OWNER_TAG, OWNER_ASSET_AMOUNT); assetTransfer.ConstructTransaction(scriptTransferOwnerAsset); CRecipient rec = {scriptTransferOwnerAsset, 0, fSubtractFeeFromAmount}; vecSend.push_back(rec); } - // Get the owner outpoints if this is a subasset - std::set myAssetOutPoints; - if (assetType == AssetType::SUB) { + // Get the owner outpoints if this is a subasset or unique asset + if (assetType == AssetType::SUB || assetType == AssetType::UNIQUE) { // Verify that this wallet is the owner for the asset, and get the owner asset outpoint - if (!VerifyAssetOwner(GetParentName(asset.strName), myAssetOutPoints, error)) { - return false; + for (auto asset : assets) { + if (!VerifyWalletHasAsset(parentName + OWNER_TAG, error)) { + return false; + } } } - if (!pwallet->CreateTransactionWithAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, asset, DecodeDestination(address), myAssetOutPoints, assetType)) { + if (!pwallet->CreateTransactionWithAssets(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coinControl, assets, DecodeDestination(address), assetType)) { if (!fSubtractFeeFromAmount && burnAmount + nFeeRequired > curBalance) strTxError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); error = std::make_pair(RPC_WALLET_ERROR, strTxError); @@ -2177,10 +2608,10 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: return true; } -bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissueAsset, const std::string& address, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +bool CreateReissueAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const CReissueAsset& reissueAsset, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { std::string asset_name = reissueAsset.strName; - std::string change_address = changeAddress; + std::string change_address = EncodeDestination(coinControl.destChange); // Check that validitity of the address if (!IsValidDestinationString(address)) { @@ -2195,24 +2626,15 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu return false; } } else { - // Create a new address - std::string strAccount; - - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - - // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwallet->GetKeyFromPool(newKey)) { - error = std::make_pair(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + CKeyID keyID; + std::string strFailReason; + if (!pwallet->CreateNewChangeAddress(reservekey, keyID, strFailReason)) { + error = std::make_pair(RPC_WALLET_KEYPOOL_RAN_OUT, strFailReason); return false; } - CKeyID keyID = newKey.GetID(); - - pwallet->SetAddressBook(keyID, strAccount, "receive"); change_address = EncodeDestination(keyID); + coinControl.destChange = DecodeDestination(change_address); } // Check the assets name @@ -2246,8 +2668,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu } // Verify that this wallet is the owner for the asset, and get the owner asset outpoint - std::set myAssetOutPoints; - if (!VerifyAssetOwner(asset_name, myAssetOutPoints, error)) { + if (!VerifyWalletHasAsset(asset_name + OWNER_TAG, error)) { return false; } @@ -2277,9 +2698,6 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu // Get the script for the burn address CScript scriptPubKeyBurn = GetScriptForDestination(DecodeDestination(Params().ReissueAssetBurnAddress())); - CCoinControl coin_control; - coin_control.destChange = DecodeDestination(change_address); - // Create and send the transaction std::string strTxError; std::vector vecSend; @@ -2289,7 +2707,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu CRecipient recipient2 = {scriptTransferOwnerAsset, 0, fSubtractFeeFromAmount}; vecSend.push_back(recipient); vecSend.push_back(recipient2); - if (!pwallet->CreateTransactionWithReissueAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, reissueAsset, DecodeDestination(address), myAssetOutPoints)) { + if (!pwallet->CreateTransactionWithReissueAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coinControl, reissueAsset, DecodeDestination(address))) { if (!fSubtractFeeFromAmount && burnAmount + nFeeRequired > curBalance) strTxError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); error = std::make_pair(RPC_WALLET_ERROR, strTxError); @@ -2298,7 +2716,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu return true; } -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { // Initialize Values for transaction std::string strTxError; @@ -2319,7 +2737,6 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa return false; } - std::set myAssetOutPoints; // Loop through all transfers and create scriptpubkeys for them for (auto transfer : vTransfers) { std::string address = transfer.second; @@ -2336,27 +2753,17 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa return false; } - std::set myTempAssetOutPoints; - if (!passets->GetAssetsOutPoints(asset_name, myTempAssetOutPoints)) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name); + if (!VerifyWalletHasAsset(asset_name, error)) // Sets error if it fails return false; - } - - if (myTempAssetOutPoints.size() == 0) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name); - return false; - } - - // Put the outpoints into our master set - myAssetOutPoints.insert(myTempAssetOutPoints.begin(), myTempAssetOutPoints.end()); // If it is an ownership transfer, make a quick check to make sure the amount is 1 - if (IsAssetNameAnOwner(asset_name)) - if (nAmount != COIN * 1) { + if (IsAssetNameAnOwner(asset_name)) { + if (nAmount != OWNER_ASSET_AMOUNT) { error = std::make_pair(RPC_INVALID_PARAMS, std::string( "When transfer an 'Ownership Asset' the amount must always be 1. Please try again with the amount of 1")); return false; } + } // Get the script for the burn address CScript scriptPubKey = GetScriptForDestination(DecodeDestination(address)); @@ -2369,10 +2776,8 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa vecSend.push_back(recipient); } - CCoinControl coin_control; - // Create and send the transaction - if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, myAssetOutPoints)) { + if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coinControl)) { if (!fSubtractFeeFromAmount && nFeeRequired > curBalance) { error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired))); return false; @@ -2390,35 +2795,62 @@ bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason())); return false; } + txid = transaction.GetHash().GetHex(); return true; } -bool VerifyAssetOwner(const std::string& asset_name, std::set& myOwnerOutPoints, std::pair& error) +bool VerifyWalletHasAsset(const std::string& asset_name, std::pair& pairError) { - // Check to make sure this wallet is the owner of the asset - if(!CheckAssetOwner(asset_name)) { - error = std::make_pair(RPC_INVALID_PARAMS, - std::string("This wallet is not the owner of the asset: ") + asset_name); + CWallet* pwallet; + if (vpwallets.size() > 0) + pwallet = vpwallets[0]; + else { + pairError = std::make_pair(RPC_WALLET_ERROR, strprintf("Wallet not found. Can't verify if it contains: %s", asset_name)); return false; } - // Get the outpoint that belongs to the Owner Asset - if (!passets->GetAssetsOutPoints(asset_name + OWNER_TAG, myOwnerOutPoints)) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet can't find the owner token information for: ") + asset_name); - return false; - } + std::vector vCoins; + std::map > mapAssetCoins; + pwallet->AvailableAssets(mapAssetCoins); - // Check to make sure we have the right amount of outpoints - if (myOwnerOutPoints.size() == 0) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name + OWNER_TAG); - return false; - } + if (mapAssetCoins.count(asset_name)) + return true; - if (myOwnerOutPoints.size() != 1) { - error = std::make_pair(RPC_INVALID_PARAMS, "Found multiple Owner Assets. Database is out of sync. You might have to run the wallet with -reindex"); + pairError = std::make_pair(RPC_INVALID_REQUEST, strprintf("Wallet doesn't have asset: %s", asset_name)); + return false; +} + +// Return true if the amount is valid with the units passed in +bool CheckAmountWithUnits(const CAmount& nAmount, const uint8_t nUnits) +{ + return nAmount % int64_t(pow(10, (MAX_UNIT - nUnits))) == 0; +} + +bool CheckEncodedIPFS(const std::string& hash, std::string& strError) +{ + if (hash.substr(0, 2) != "Qm") { + strError = _("Invalid parameter: ipfs_hash must start with 'Qm'."); return false; } return true; +} + +void GetTxOutAssetTypes(const std::vector& vout, int& issues, int& reissues, int& transfers, int& owners) +{ + for (auto out: vout) { + int type; + bool fIsOwner; + if (out.scriptPubKey.IsAssetScript(type, fIsOwner)) { + if (type == TX_NEW_ASSET && !fIsOwner) + issues++; + else if (type == TX_NEW_ASSET && fIsOwner) + owners++; + else if (type == TX_TRANSFER_ASSET) + transfers++; + else if (type == TX_REISSUE_ASSET) + reissues++; + } + } } \ No newline at end of file diff --git a/src/assets/assets.h b/src/assets/assets.h index a67ea46839..fe2d44825e 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -6,17 +6,15 @@ #ifndef RAVENCOIN_ASSET_PROTOCOL_H #define RAVENCOIN_ASSET_PROTOCOL_H -#include +#include "amount.h" +#include "tinyformat.h" +#include "assettypes.h" #include #include #include #include #include -#include -#include "serialize.h" -#include "primitives/transaction.h" -#include "assettypes.h" #define RVN_R 114 #define RVN_V 118 @@ -25,16 +23,22 @@ #define RVN_T 116 #define RVN_O 111 +#define DEFAULT_UNITS 0 +#define DEFAULT_REISSUABLE 1 +#define DEFAULT_HAS_IPFS 0 +#define DEFAULT_IPFS "" +#define MIN_ASSET_LENGTH 3 #define OWNER_TAG "!" #define OWNER_LENGTH 1 #define OWNER_UNITS 0 -#define MIN_ASSET_LENGTH 3 #define OWNER_ASSET_AMOUNT 1 * COIN +#define UNIQUE_ASSET_AMOUNT 1 * COIN +#define UNIQUE_ASSET_UNITS 0 +#define UNIQUE_ASSETS_REISSUABLE 0 #define ASSET_TRANSFER_STRING "transfer_asset" #define ASSET_NEW_STRING "new_asset" #define ASSET_REISSUE_STRING "reissue_asset" -#define ASSET_RESERVED_STRING "reserved_asset" class CScript; class CDataStream; @@ -45,10 +49,17 @@ class CWallet; class CReserveKey; class CWalletTx; struct CAssetOutputEntry; +class CCoinControl; +struct CBlockAssetUndo; // 50000 * 82 Bytes == 4.1 Mb #define MAX_CACHE_ASSETS_SIZE 50000 +// Create map that store that state of current reissued transaction that the mempool as accepted. +// If an asset name is in this map, any other reissue transactions wont be accepted into the mempool +extern std::map mapReissuedTx; +extern std::map mapReissuedAssets; + class CAssets { public: std::map > mapMyUnspentAssets; // Asset Name -> COutPoint @@ -120,12 +131,12 @@ public : std::set setPossiblyMineAdd; std::set setPossiblyMineRemove; - CAssetsCache() + CAssetsCache() : CAssets() { SetNull(); } - CAssetsCache(const CAssetsCache& cache) + CAssetsCache(const CAssetsCache& cache) : CAssets(cache) { this->mapMyUnspentAssets = cache.mapMyUnspentAssets; this->mapAssetsAddressAmount = cache.mapAssetsAddressAmount; @@ -228,11 +239,11 @@ public : bool RemoveNewAsset(const CNewAsset& asset, const std::string address); bool RemoveTransfer(const CAssetTransfer& transfer, const std::string& address, const COutPoint& out); bool RemoveOwnerAsset(const std::string& assetsName, const std::string address); - bool RemoveReissueAsset(const CReissueAsset& reissue, const std::string address, const COutPoint& out, const std::vector >& vUndoIPFS); + bool RemoveReissueAsset(const CReissueAsset& reissue, const std::string address, const COutPoint& out, const std::vector >& vUndoIPFS); bool UndoAssetCoin(const Coin& coin, const COutPoint& out); // Cache only add asset functions - bool AddNewAsset(const CNewAsset& asset, const std::string address); + bool AddNewAsset(const CNewAsset& asset, const std::string address, const int& nHeight, const uint256& blockHash); bool AddTransferAsset(const CAssetTransfer& transferAsset, const std::string& address, const COutPoint& out, const CTxOut& txOut); bool AddOwnerAsset(const std::string& assetsName, const std::string address); bool AddToMyUpspentOutPoints(const std::string& strName, const COutPoint& out); @@ -247,8 +258,9 @@ public : bool ContainsAsset(const std::string& assetName); bool AddPossibleOutPoint(const CAssetCachePossibleMine& possibleMine); - bool CheckIfAssetExists(const std::string& name); - bool GetAssetIfExists(const std::string& name, CNewAsset& asset); + bool CheckIfAssetExists(const std::string& name, bool fForceDuplicateCheck = true); + bool GetAssetMetaDataIfExists(const std::string &name, CNewAsset &asset, int& nHeight, uint256& blockHash); + bool GetAssetMetaDataIfExists(const std::string &name, CNewAsset &asset); //! Calculate the size of the CAssets (in bytes) size_t DynamicMemoryUsage() const; @@ -299,26 +311,35 @@ CAmount GetReissueAssetBurnAmount(); CAmount GetIssueSubAssetBurnAmount(); CAmount GetIssueUniqueAssetBurnAmount(); CAmount GetBurnAmount(const AssetType type); +CAmount GetBurnAmount(const int nType); std::string GetBurnAddress(const AssetType type); +std::string GetBurnAddress(const int nType); + +void GetTxOutAssetTypes(const std::vector& vout, int& issues, int& reissues, int& transfers, int& owners); bool IsAssetNameValid(const std::string& name); bool IsAssetNameValid(const std::string& name, AssetType& assetType); +bool IsAssetNameValid(const std::string& name, AssetType& assetType, std::string& error); +bool IsUniqueTagValid(const std::string& tag); bool IsAssetNameAnOwner(const std::string& name); std::string GetParentName(const std::string& name); // Gets the parent name of a subasset TEST/TESTSUB would return TEST +std::string GetUniqueAssetName(const std::string& parent, const std::string& tag); -bool IsAssetNameSizeValid(const std::string& name); +bool IsTypeCheckNameValid(const AssetType type, const std::string& name, std::string& error); bool IsAssetUnitsValid(const CAmount& units); bool AssetFromTransaction(const CTransaction& tx, CNewAsset& asset, std::string& strAddress); bool OwnerFromTransaction(const CTransaction& tx, std::string& ownerName, std::string& strAddress); bool ReissueAssetFromTransaction(const CTransaction& tx, CReissueAsset& reissue, std::string& strAddress); +bool UniqueAssetFromTransaction(const CTransaction& tx, CNewAsset& asset, std::string& strAddress); bool TransferAssetFromScript(const CScript& scriptPubKey, CAssetTransfer& assetTransfer, std::string& strAddress); bool AssetFromScript(const CScript& scriptPubKey, CNewAsset& asset, std::string& strAddress); bool OwnerAssetFromScript(const CScript& scriptPubKey, std::string& assetName, std::string& strAddress); bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, std::string& strAddress); +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued); bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type); bool CheckReissueBurnTx(const CTxOut& txOut); @@ -327,26 +348,34 @@ bool CheckOwnerDataTx(const CTxOut& txOut); bool CheckReissueDataTx(const CTxOut& txOut); bool CheckTransferOwnerTx(const CTxOut& txOut); +bool CheckEncodedIPFS(const std::string& hash, std::string& strError); + +bool CheckAmountWithUnits(const CAmount& nAmount, const uint8_t nUnits); + bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex); +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptOwnerAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptReissueAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptTransferAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptNewAsset(const CScript& scriptPubKey); +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey); bool IsScriptOwnerAsset(const CScript& scriptPubKey); bool IsScriptReissueAsset(const CScript& scriptPubKey); bool IsScriptTransferAsset(const CScript& scriptPubKey); bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg); -bool CheckAssetOwner(const std::string& assetName); -void GetAllOwnedAssets(std::vector& names); -void GetAllMyAssets(std::vector& names); +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names, int nMinConf = 1); +void GetAllMyAssets(CWallet* pwallet, std::vector& names, int nMinConf = 1, bool fIncludeAdministrator = false, bool fOnlyAdministrator = false); +/** TO BE USED ONLY ON STARTUP */ +void GetAllMyAssetsFromCache(std::vector& names); void UpdatePossibleAssets(); -bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount); +bool GetAssetInfoFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount); +bool GetAssetInfoFromScript(const CScript& scriptPubKey, std::string& strName, CAmount& nAmount); -void GetAssetData(const CScript& script, CAssetOutputEntry& data); +bool GetAssetData(const CScript& script, CAssetOutputEntry& data); bool GetBestAssetAddressAmount(CAssetsCache& cache, const std::string& assetName, const std::string& address); @@ -356,13 +385,15 @@ bool GetMyAssetBalance(CAssetsCache& cache, const std::string& assetName, CAmoun bool GetMyAssetBalances(CAssetsCache& cache, const std::vector& assetNames, std::map& balances); bool GetMyAssetBalances(CAssetsCache& cache, std::map& balances); -bool VerifyAssetOwner(const std::string& asset_name, std::set& myOwnerOutPoints, std::pair& error); +/** Verifies that this wallet owns the give asset */ +bool VerifyWalletHasAsset(const std::string& asset_name, std::pair& pairError); std::string DecodeIPFS(std::string encoded); std::string EncodeIPFS(std::string decoded); -bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); -bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& asset, const std::string& address, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const CNewAsset& asset, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const std::vector assets, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateReissueAssetTransaction(CWallet* pwallet, CCoinControl& coinControl, const CReissueAsset& asset, const std::string& address, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& reserveKey, std::pair& error, std::string& txid); #endif //RAVENCOIN_ASSET_PROTOCOL_H diff --git a/src/assets/assettypes.cpp b/src/assets/assettypes.cpp new file mode 100644 index 0000000000..8d38e34b9a --- /dev/null +++ b/src/assets/assettypes.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assettypes.h" + +int IntFromAssetType(AssetType type) { + return (int)type; +} + +AssetType AssetTypeFromInt(int nType) { + return (AssetType)nType; +} \ No newline at end of file diff --git a/src/assets/assettypes.h b/src/assets/assettypes.h index f304703cf4..2705f22e1b 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -1,33 +1,69 @@ -// +// Copyright (c) 2018 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. // Created by Jeremy Anderson on 5/15/18. -// #ifndef RAVENCOIN_NEWASSET_H #define RAVENCOIN_NEWASSET_H -#include #include +#include +#include #include -#include "serialize.h" +#include "amount.h" +#include "primitives/transaction.h" #define MAX_UNIT 8 +#define MIN_UNIT 0 class CAssetsCache; +enum class AssetType +{ + ROOT = 0, + SUB = 1, + UNIQUE = 2, + OWNER = 3, + MSGCHANNEL = 4, + VOTE = 5, + REISSUE = 6, + INVALID = 7 +}; + +int IntFromAssetType(AssetType type); +AssetType AssetTypeFromInt(int nType); -enum AssetType +const char IPFS_SHA2_256 = 0x12; +const char IPFS_SHA2_256_LEN = 0x20; + +template +void ReadWriteIPFSHash(Stream& s, Operation ser_action, std::string& strIPFSHash) { - ROOT, - OWNER, - SUB, - UNIQUE, - MSGCHANNEL, - VOTE, - INVALID, - REISSUE + // assuming 34-byte IPFS SHA2-256 decoded hash (0x12, 0x20, 32 more bytes) + if (ser_action.ForRead()) + { + strIPFSHash = ""; + if (!s.empty() and s.size() >= 34) { + char _sha2_256; + ::Unserialize(s, _sha2_256); + std::basic_string hash; + ::Unserialize(s, hash); + std::ostringstream os; + os << IPFS_SHA2_256 << IPFS_SHA2_256_LEN << hash.substr(0, 32); + strIPFSHash = os.str(); + } + } + else + { + if (strIPFSHash.length() == 34) { + ::Serialize(s, IPFS_SHA2_256); + ::Serialize(s, strIPFSHash.substr(2)); + } + } }; -class CNewAsset { +class CNewAsset +{ public: std::string strName; // MAX 31 Bytes CAmount nAmount; // 8 Bytes @@ -42,6 +78,7 @@ class CNewAsset { } CNewAsset(const std::string& strName, const CAmount& nAmount, const int& units, const int& nReissuable, const int& nHasIPFS, const std::string& strIPFSHash); + CNewAsset(const std::string& strName, const CAmount& nAmount); CNewAsset(const CNewAsset& asset); CNewAsset& operator=(const CNewAsset& asset); @@ -58,7 +95,7 @@ class CNewAsset { bool IsNull() const; - bool IsValid(std::string& strError, CAssetsCache& assetCache, bool fCheckMempool = false, bool fCheckDuplicateInputs = true) const; + bool IsValid(std::string& strError, CAssetsCache& assetCache, bool fCheckMempool = false, bool fCheckDuplicateInputs = true, bool fForceDuplicateCheck = true) const; std::string ToString(); @@ -68,13 +105,16 @@ class CNewAsset { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); READWRITE(units); READWRITE(nReissuable); READWRITE(nHasIPFS); - READWRITE(strIPFSHash); + if (nHasIPFS == 1) { + ReadWriteIPFSHash(s, ser_action, strIPFSHash); + } } }; @@ -87,7 +127,36 @@ class AssetComparator } }; -class CAssetTransfer { +class CDatabasedAssetData +{ +public: + CNewAsset asset; + int nHeight; + uint256 blockHash; + + CDatabasedAssetData(const CNewAsset& asset, const int& nHeight, const uint256& blockHash); + CDatabasedAssetData(); + + void SetNull() + { + asset.SetNull(); + nHeight = -1; + blockHash = uint256(); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(asset); + READWRITE(nHeight); + READWRITE(blockHash); + } +}; + +class CAssetTransfer +{ public: std::string strName; CAmount nAmount; @@ -107,7 +176,8 @@ class CAssetTransfer { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); } @@ -117,10 +187,12 @@ class CAssetTransfer { void ConstructTransaction(CScript& script) const; }; -class CReissueAsset { +class CReissueAsset +{ public: std::string strName; CAmount nAmount; + int8_t nUnits; int8_t nReissuable; std::string strIPFSHash; @@ -133,6 +205,7 @@ class CReissueAsset { { nAmount = 0; strName = ""; + nUnits = 0; nReissuable = 1; strIPFSHash = ""; } @@ -140,14 +213,16 @@ class CReissueAsset { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); + READWRITE(nUnits); READWRITE(nReissuable); - READWRITE(strIPFSHash); + ReadWriteIPFSHash(s, ser_action, strIPFSHash); } - CReissueAsset(const std::string& strAssetName, const CAmount& nAmount, const int& nReissuable, const std::string& strIPFSHash); + CReissueAsset(const std::string& strAssetName, const CAmount& nAmount, const int& nUnits, const int& nReissuable, const std::string& strIPFSHash); bool IsValid(std::string& strError, CAssetsCache& assetCache) const; void ConstructTransaction(CScript& script) const; bool IsNull() const; @@ -159,14 +234,19 @@ struct CAssetCacheNewAsset { CNewAsset asset; std::string address; + uint256 blockHash; + int blockHeight; - CAssetCacheNewAsset(const CNewAsset& asset, const std::string& address) + CAssetCacheNewAsset(const CNewAsset& asset, const std::string& address, const int& blockHeight, const uint256& blockHash) { this->asset = asset; this->address = address; + this->blockHash = blockHash; + this->blockHeight = blockHeight; } - bool operator<(const CAssetCacheNewAsset& rhs) const { + bool operator<(const CAssetCacheNewAsset& rhs) const + { return asset.strName < rhs.asset.strName; } }; @@ -176,15 +256,21 @@ struct CAssetCacheReissueAsset CReissueAsset reissue; std::string address; COutPoint out; + uint256 blockHash; + int blockHeight; - CAssetCacheReissueAsset(const CReissueAsset& reissue, const std::string& address, const COutPoint& out) + + CAssetCacheReissueAsset(const CReissueAsset& reissue, const std::string& address, const COutPoint& out, const int& blockHeight, const uint256& blockHash) { this->reissue = reissue; this->address = address; this->out = out; + this->blockHash = blockHash; + this->blockHeight = blockHeight; } - bool operator<(const CAssetCacheReissueAsset& rhs) const { + bool operator<(const CAssetCacheReissueAsset& rhs) const + { return out < rhs.out; } @@ -205,17 +291,7 @@ struct CAssetCacheNewTransfer bool operator<(const CAssetCacheNewTransfer& rhs ) const { - return out < rhs.out; -// if (transfer.strName < rhs.transfer.strName) -// return true; -// -// if (rhs.transfer.strName == transfer.strName && address == rhs.address && transfer.nAmount == rhs.transfer.nAmount && out == rhs.out) { -// return false; -// } else if (rhs.transfer.strName == transfer.strName && (address != rhs.address || transfer.nAmount != rhs.transfer.nAmount || out == rhs.out)) -// return true; -// -// return false; } }; @@ -230,11 +306,10 @@ struct CAssetCacheNewOwner this->address = address; } - bool operator<(const CAssetCacheNewOwner& rhs) const { + bool operator<(const CAssetCacheNewOwner& rhs) const + { return assetName < rhs.assetName; -// if (assetName < rhs.assetName) -// return true; } }; @@ -287,12 +362,14 @@ struct CAssetCachePossibleMine // Least Recently Used Cache template -class CLRUCache { +class CLRUCache +{ public: typedef typename std::pair key_value_pair_t; typedef typename std::list::iterator list_iterator_t; - CLRUCache(size_t max_size) : maxSize(max_size) { + CLRUCache(size_t max_size) : maxSize(max_size) + { } CLRUCache() { @@ -303,13 +380,15 @@ class CLRUCache { { auto it = cacheItemsMap.find(key); cacheItemsList.push_front(key_value_pair_t(key, value)); - if (it != cacheItemsMap.end()) { + if (it != cacheItemsMap.end()) + { cacheItemsList.erase(it->second); cacheItemsMap.erase(it); } cacheItemsMap[key] = cacheItemsList.begin(); - if (cacheItemsMap.size() > maxSize) { + if (cacheItemsMap.size() > maxSize) + { auto last = cacheItemsList.end(); last--; cacheItemsMap.erase(last->first); @@ -320,7 +399,8 @@ class CLRUCache { void Erase(const cache_key_t& key) { auto it = cacheItemsMap.find(key); - if (it != cacheItemsMap.end()) { + if (it != cacheItemsMap.end()) + { cacheItemsList.erase(it->second); cacheItemsMap.erase(it); } @@ -329,9 +409,12 @@ class CLRUCache { const cache_value_t& Get(const cache_key_t& key) { auto it = cacheItemsMap.find(key); - if (it == cacheItemsMap.end()) { + if (it == cacheItemsMap.end()) + { throw std::range_error("There is no such key in cache"); - } else { + } + else + { cacheItemsList.splice(cacheItemsList.begin(), cacheItemsList, it->second); return it->second->second; } diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 22e2c76f0f..24495c6993 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -36,14 +36,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].nValue = 50 * CENT; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; - AddCoins(coinsRet, dummyTransactions[0], 0); + AddCoins(coinsRet, dummyTransactions[0], 0, uint256()); dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21 * CENT; dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[1].nValue = 22 * CENT; dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); - AddCoins(coinsRet, dummyTransactions[1], 0); + AddCoins(coinsRet, dummyTransactions[1], 0, uint256()); return dummyTransactions; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e08b97b765..bf06e1a47d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -128,15 +128,14 @@ class CMainParams : public CChainParams { consensus.nPowTargetSpacing = 1 * 60; consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; - consensus.nRuleChangeActivationThreshold = 1632; // Approx 80% of 2018 + consensus.nRuleChangeActivationThreshold = 1814; // Approx 90% of 2016 consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 5; //Assets (RIP2) - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 1604102400; // TODO Update for october 31st 2018 build - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 1604108400; // TODO Update for october 31st 2018 build - + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 6; //Assets (RIP2) + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 1540944000; // Oct 31, 2018 + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 1572480000; // Oct 31, 2019 // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -173,8 +172,9 @@ class CMainParams : public CChainParams { assert(consensus.hashGenesisBlock == uint256S("0000006b444bc2f2ffe627be9d9e7e7a0730000870ef6eb6da46c8eae389df90")); assert(genesis.hashMerkleRoot == uint256S("28ff00a867739a352523808d301f504bc4547699398d70faf2266a8bae5f3516")); - vSeeds.emplace_back("seed-raven.ravencoin.org", false); vSeeds.emplace_back("seed-raven.bitactivate.com", false); + vSeeds.emplace_back("seed-raven.ravencoin.com", false); + vSeeds.emplace_back("seed-raven.ravencoin.org", false); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,60); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,122); @@ -221,12 +221,16 @@ class CMainParams : public CChainParams { // DGW Activation nDGWActivationBlock = 338778; + + nMaxReorganizationDepth = 60; // 60 at 1 minute block timespan is +/- 60 minutes. + nMinReorganizationPeers = 4; + nMinReorganizationAge = 60 * 60 * 12; // 12 hours /** RVN End **/ } }; /** - * Testnet (v4) + * Testnet (v6) */ class CTestNetParams : public CChainParams { public: @@ -251,7 +255,7 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 5; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 1533924000; // GMT: Friday, August 10, 2018 6:00:00 PM - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 1536516000; // GMT: Sunday, September 9, 2018 6:00:00 PM + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 1538351999; // GMT: Sunday, September 30, 2018 11:59:59 PM // The best chain should have at least this much work. @@ -265,9 +269,11 @@ class CTestNetParams : public CChainParams { pchMessageStart[1] = 0x56; pchMessageStart[2] = 0x4E; pchMessageStart[3] = 0x54; - nDefaultPort = 18768; + nDefaultPort = 18770; nPruneAfterHeight = 1000; + uint32_t nGenesisTime = 1537466400; // Thursday, September 20, 2018 12:00:00 PM GMT-06:00 + // This is used inorder to mine the genesis block. Once found, we can use the nonce and block hash found to create a valid genesis block // ///////////////////////////////////////////////////////////////// @@ -282,7 +288,7 @@ class CTestNetParams : public CChainParams { // uint256 TempHashHolding = uint256S("0x0000000000000000000000000000000000000000000000000000000000000000"); // uint256 BestBlockHash = uint256S("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // for (int i=0;i<40000000;i++) { -// genesis = CreateGenesisBlock(1533751200, i, 0x1e00ffff, 2, 5000 * COIN); +// genesis = CreateGenesisBlock(nGenesisTime, i, 0x1e00ffff, 2, 5000 * COIN); // //genesis.hashPrevBlock = TempHashHolding; // consensus.hashGenesisBlock = genesis.GetHash(); // @@ -328,18 +334,19 @@ class CTestNetParams : public CChainParams { // ///////////////////////////////////////////////////////////////// - genesis = CreateGenesisBlock(1533751200, 555705, 0x1e00ffff, 2, 5000 * COIN); + genesis = CreateGenesisBlock(nGenesisTime, 15615880, 0x1e00ffff, 2, 5000 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); //Test MerkleRoot and GenesisBlock - assert(consensus.hashGenesisBlock == uint256S("0x0000006ebc14cb6777bedda407702dfbc6b273f1af956bcfd6f4f98a2eb14433")); - assert(genesis.hashMerkleRoot == uint256S("0x28ff00a867739a352523808d301f504bc4547699398d70faf2266a8bae5f3516")); + assert(consensus.hashGenesisBlock == uint256S("0x000000ecfc5e6324a079542221d00e10362bdc894d56500c414060eea8a3ad5a")); + assert(genesis.hashMerkleRoot == uint256S("28ff00a867739a352523808d301f504bc4547699398d70faf2266a8bae5f3516")); vFixedSeeds.clear(); vSeeds.clear(); - vSeeds.emplace_back("seed-testnet-raven.ravencoin.org", false); vSeeds.emplace_back("seed-testnet-raven.bitactivate.com", false); + vSeeds.emplace_back("seed-testnet-raven.ravencoin.com", false); + vSeeds.emplace_back("seed-testnet-raven.ravencoin.org", false); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); @@ -384,8 +391,13 @@ class CTestNetParams : public CChainParams { strGlobalBurnAddress = "n1BurnXXXXXXXXXXXXXXXXXXXXXXU1qejP"; // DGW Activation - nDGWActivationBlock = 1440; + nDGWActivationBlock = 200; + + nMaxReorganizationDepth = 60; // 60 at 1 minute block timespan is +/- 60 minutes. + nMinReorganizationPeers = 4; + nMinReorganizationAge = 60 * 60 * 12; // 12 hours /** RVN End **/ + } }; @@ -412,7 +424,7 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 999999999999ULL; - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 6; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 999999999999ULL; @@ -539,6 +551,10 @@ class CRegTestParams : public CChainParams { // DGW Activation nDGWActivationBlock = 200; + + nMaxReorganizationDepth = 60; // 60 at 1 minute block timespan is +/- 60 minutes. + nMinReorganizationPeers = 4; + nMinReorganizationAge = 60 * 60 * 12; // 12 hours /** RVN End **/ } }; diff --git a/src/chainparams.h b/src/chainparams.h index 56279be0d6..01c903eb84 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -102,6 +102,10 @@ class CChainParams const std::string& GlobalBurnAddress() const { return strGlobalBurnAddress; } unsigned int DGWActivationBlock() const { return nDGWActivationBlock; } + + int MaxReorganizationDepth() const { return nMaxReorganizationDepth; } + int MinReorganizationPeers() const { return nMinReorganizationPeers; } + int MinReorganizationAge() const { return nMinReorganizationAge; } /** RVN End **/ protected: @@ -140,6 +144,10 @@ class CChainParams std::string strGlobalBurnAddress; unsigned int nDGWActivationBlock; + + int nMaxReorganizationDepth; + int nMinReorganizationPeers; + int nMinReorganizationAge; /** RVN End **/ }; diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index bb75ab6d03..ef7e325c88 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -38,7 +38,7 @@ class CBaseMainParams : public CBaseChainParams }; /** - * Testnet (v3) + * Testnet (v6) */ class CBaseTestNetParams : public CBaseChainParams { @@ -46,7 +46,7 @@ class CBaseTestNetParams : public CBaseChainParams CBaseTestNetParams() { nRPCPort = 18766; - strDataDir = "testnet4"; + strDataDir = "testnet6"; } }; diff --git a/src/coins.cpp b/src/coins.cpp index fc74296db8..bbf4794376 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -93,7 +93,7 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi cachedCoinsUsage += it->second.coin.DynamicMemoryUsage(); } -void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check, CAssetsCache* assetsCache, std::pair* undoIPFSHash) { +void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, uint256 blockHash, bool check, CAssetsCache* assetsCache, std::pair* undoAssetData) { bool fCoinbase = tx.IsCoinBase(); const uint256& txid = tx.GetHash(); @@ -110,7 +110,7 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool OwnerFromTransaction(tx, ownerName, ownerAddress); // Add the new asset to cache - if (!assetsCache->AddNewAsset(asset, strAddress)) + if (!assetsCache->AddNewAsset(asset, strAddress, nHeight, blockHash)) error("%s : Failed at adding a new asset to our cache. asset: %s", __func__, asset.strName); @@ -138,22 +138,24 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool std::string strAddress; ReissueAssetFromTransaction(tx, reissue, strAddress); + int reissueIndex = tx.vout.size() - 1; + + // Get the asset before we change it CNewAsset asset; - if (!assetsCache->GetAssetIfExists(reissue.strName, asset)) + if (!assetsCache->GetAssetMetaDataIfExists(reissue.strName, asset)) error("%s: Failed to get the original asset that is getting reissued. Asset Name : %s", __func__, reissue.strName); - int reissueIndex = tx.vout.size() - 1; - if (!assetsCache->AddReissueAsset(reissue, strAddress, COutPoint(txid, reissueIndex))) error("%s: Failed to reissue an asset. Asset Name : %s", __func__, reissue.strName); // Set the old IPFSHash for the blockundo - if (reissue.strIPFSHash != "") { - auto pair = std::make_pair(reissue.strName, asset.strIPFSHash); - undoIPFSHash->first = reissue.strName; // Asset Name - undoIPFSHash->second = asset.strIPFSHash; // Old Assets IPFSHash + bool fIPFSChanged = !reissue.strIPFSHash.empty(); + bool fUnitsChanged = reissue.nUnits != -1; + if (fIPFSChanged || fUnitsChanged) { + undoAssetData->first = reissue.strName; // Asset Name + undoAssetData->second = CBlockAssetUndo {fIPFSChanged, fUnitsChanged, asset.strIPFSHash, asset.units}; // ipfschanged, unitchanged, Old Assets IPFSHash, old units } CAssetCachePossibleMine possibleMine(reissue.strName, COutPoint(tx.GetHash(), reissueIndex), @@ -161,6 +163,27 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool if (!assetsCache->AddPossibleOutPoint(possibleMine)) error("%s: Failed to add an reissued asset I own to my Unspent Asset Cache. Asset Name : %s", __func__, reissue.strName); + } else if (tx.IsNewUniqueAsset()) { + for (int n = 0; n < (int)tx.vout.size(); n++) { + auto out = tx.vout[n]; + + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + AssetFromScript(out.scriptPubKey, asset, strAddress); + + // Add the new asset to cache + if (!assetsCache->AddNewAsset(asset, strAddress, nHeight, blockHash)) + error("%s : Failed at adding a new asset to our cache. asset: %s", __func__, + asset.strName); + + CAssetCachePossibleMine possibleMine(asset.strName, COutPoint(tx.GetHash(), n), out); + if (!assetsCache->AddPossibleOutPoint(possibleMine)) + error("%s: Failed to add an asset I own to my Unspent Asset Cache. Asset Name : %s", + __func__, asset.strName); + } + } } } } diff --git a/src/coins.h b/src/coins.h index 5e94589543..168c97d9f3 100644 --- a/src/coins.h +++ b/src/coins.h @@ -20,6 +20,7 @@ #include #include +#include /** * A UTXO entry. @@ -309,7 +310,7 @@ class CCoinsViewCache : public CCoinsViewBacked // an overwrite. // TODO: pass in a boolean to limit these possible overwrites to known // (pre-BIP34) cases. -void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool check = false, CAssetsCache* assetsCache = nullptr, std::pair* undoIPFSHash = nullptr); +void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, uint256 blockHash, bool check = false, CAssetsCache* assetsCache = nullptr, std::pair* undoAssetData = nullptr); //! Utility function to find any unspent output with a given txid. // This function can be quite expensive because in the event of a transaction diff --git a/src/consensus/consensus.cpp b/src/consensus/consensus.cpp index c18d87768a..173e41eaed 100644 --- a/src/consensus/consensus.cpp +++ b/src/consensus/consensus.cpp @@ -19,5 +19,4 @@ unsigned int GetMaxBlockSerializedSize() return MAX_BLOCK_SERIALIZED_SIZE_RIP2; return MAX_BLOCK_SERIALIZED_SIZE; -} - +} \ No newline at end of file diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index d38961ce18..3fb84f2624 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -31,7 +31,10 @@ static const int WITNESS_SCALE_FACTOR = 4; static const size_t MIN_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 60; // 60 is the lower bound for the size of a valid serialized CTransaction static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 10; // 10 is the lower bound for the size of a serialized CTransaction -static bool fAssetsIsActive = false; +#define UNUSED_VAR __attribute__ ((unused)) +//! This variable needs to in this class because undo.h uses it. However because it is in this class +//! it causes unused variable warnings when compiling. This UNUSED_VAR removes the unused warnings +UNUSED_VAR static bool fAssetsIsActive = false; unsigned int GetMaxBlockWeight(); unsigned int GetMaxBlockSerializedSize(); diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index e2ffce48fc..7fcdd5b8cc 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -162,7 +162,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i return nSigOps; } -bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCache* assetCache, bool fCheckDuplicateInputs, bool fMemPoolCheck) +bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCache* assetCache, bool fCheckDuplicateInputs, bool fMemPoolCheck, bool fCheckAssetDuplicate, bool fForceDuplicateCheck) { // Basic checks that don't depend on any context if (tx.vin.empty()) @@ -186,24 +186,47 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); /** RVN START */ - if (!AreAssetsDeployed() && txout.scriptPubKey.IsAssetScript() && !fReindex) + bool isAsset = false; + int nType; + bool fIsOwner; + if (txout.scriptPubKey.IsAssetScript(nType, fIsOwner)) + isAsset = true; + + // Make sure that all asset tx have a nValue of zero RVN + if (isAsset && txout.nValue != 0) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-asset-tx-amount-isn't-zero"); + + if (!AreAssetsDeployed() && isAsset && !fReindex) return state.DoS(100, false, REJECT_INVALID, "bad-txns-is-asset-and-asset-not-active"); // Check for transfers that don't meet the assets units only if the assetCache is not null - if (AreAssetsDeployed()) { + if (AreAssetsDeployed() && isAsset) { if (assetCache) { // Get the transfer transaction data from the scriptPubKey - if (txout.scriptPubKey.IsTransferAsset()) { + if ( nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; std::string address; if (!TransferAssetFromScript(txout.scriptPubKey, transfer, address)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-bad-deserialize"); + // Check asset name validity and get type + AssetType assetType; + if (!IsAssetNameValid(transfer.strName, assetType)) { + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-name-invalid"); + } + // If the transfer is an ownership asset. Check to make sure that it is OWNER_ASSET_AMOUNT if (IsAssetNameAnOwner(transfer.strName)) { if (transfer.nAmount != OWNER_ASSET_AMOUNT) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); } + + // If the transfer is a unique asset. Check to make sure that it is UNIQUE_ASSET_AMOUNT + if (assetType == AssetType::UNIQUE) { + if (transfer.nAmount != UNIQUE_ASSET_AMOUNT) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-unique-amount-was-not-1"); + } + } } } @@ -234,8 +257,10 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa /** RVN START */ if (AreAssetsDeployed()) { if (assetCache) { - // Get the new asset from the transaction if (tx.IsNewAsset()) { + if(!tx.VerifyNewAsset()) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-verifying-issue-asset"); + CNewAsset asset; std::string strAddress; if (!AssetFromTransaction(tx, asset, strAddress)) @@ -246,10 +271,11 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (!IsNewOwnerTxValid(tx, asset.strName, strAddress, strError)) return state.DoS(100, false, REJECT_INVALID, strError); - if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckDuplicateInputs)) + if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckAssetDuplicate, fForceDuplicateCheck)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-" + strError); } else if (tx.IsReissueAsset()) { + CReissueAsset reissue; std::string strAddress; if (!ReissueAssetFromTransaction(tx, reissue, strAddress)) @@ -269,6 +295,66 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (!foundOwnerAsset) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-bad-owner-asset"); + } else if (tx.IsNewUniqueAsset()) { + + std::string assetRoot = ""; + int assetCount = 0; + + for (auto out : tx.vout) { + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset"); + + std::string strError = ""; + if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckAssetDuplicate, fForceDuplicateCheck)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-" + strError); + + std::string root = GetParentName(asset.strName); + if (assetRoot.compare("") == 0) + assetRoot = root; + if (assetRoot.compare(root) != 0) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-mismatched-root"); + + assetCount += 1; + } + } + + if (assetCount < 1) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-no-outputs"); + + bool foundOwnerAsset = false; + for (auto out : tx.vout) { + CAssetTransfer transfer; + std::string transferAddress; + if (TransferAssetFromScript(out.scriptPubKey, transfer, transferAddress)) { + if (assetRoot + OWNER_TAG == transfer.strName) { + foundOwnerAsset = true; + break; + } + } + } + + if (!foundOwnerAsset) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-bad-owner-asset"); + } else { + // Fail if transaction contains any non-transfer asset scripts and hasn't conformed to one of the + // above transaction types. Also fail if it contains OP_RVN_ASSET opcode but wasn't a valid script. + for (auto out : tx.vout) { + int nType; + bool _isOwner; + if (out.scriptPubKey.IsAssetScript(nType, _isOwner)) { + if (nType != TX_TRANSFER_ASSET) { + return state.DoS(100, false, REJECT_INVALID, "bad-txns-bad-asset-transaction"); + } + } else { + if (out.scriptPubKey.Find(OP_RVN_ASSET) > 0) { + return state.DoS(100, false, REJECT_INVALID, "bad-txns-bad-asset-script"); + } + } + } } } } @@ -322,7 +408,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c } //! Check to make sure that the inputs and outputs CAmount match exactly. -bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs) +bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, std::vector >& vPairReissueAssets, const bool fRunningUnitTests) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { @@ -342,7 +428,7 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c std::string strName; CAmount nAmount; - if (!GetAssetFromCoin(coin, strName, nAmount)) + if (!GetAssetInfoFromCoin(coin, strName, nAmount)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-failed-to-get-asset-from-script"); // Add to the total value of assets in the inputs @@ -369,21 +455,22 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c else totalOutputs.insert(make_pair(transfer.strName, transfer.nAmount)); - if (IsAssetNameAnOwner(transfer.strName)) { - if (transfer.nAmount != OWNER_ASSET_AMOUNT) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); - } else { + if (!fRunningUnitTests) { + if (IsAssetNameAnOwner(transfer.strName)) { + if (transfer.nAmount != OWNER_ASSET_AMOUNT) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); + } else { // For all other types of assets, make sure they are sending the right type of units CNewAsset asset; - if (!passets->GetAssetIfExists(transfer.strName, asset)) + if (!passets->GetAssetMetaDataIfExists(transfer.strName, asset)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-not-exist"); if (asset.strName != transfer.strName) return state.DoS(100, false, REJECT_INVALID, "bad-txns-asset-database-corrupted"); - if (transfer.nAmount % int64_t(pow(10, (MAX_UNIT - asset.units))) != 0) - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-transfer-asset-amount-not-match-units"); + if (!CheckAmountWithUnits(transfer.nAmount, asset.units)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-amount-not-match-units"); + } } } else if (txout.scriptPubKey.IsReissueAsset()) { CReissueAsset reissue; @@ -391,26 +478,40 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c if (!ReissueAssetFromScript(txout.scriptPubKey, reissue, address)) return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-reissue-bad-deserialize"); - std::string strError; - if (!reissue.IsValid(strError, *passets)) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns" + strError); + if (!fRunningUnitTests) { + std::string strError; + if (!reissue.IsValid(strError, *passets)) { + return state.DoS(100, false, REJECT_INVALID, + "bad-txns" + strError); + } } + if (mapReissuedAssets.count(reissue.strName)) { + if (mapReissuedAssets.at(reissue.strName) != tx.GetHash()) + return state.DoS(100, false, REJECT_INVALID, "bad-tx-reissue-chaining-not-allowed"); + } else { + vPairReissueAssets.emplace_back(std::make_pair(reissue.strName, tx.GetHash())); + } } } - // Check the input values and the output values - if (totalOutputs.size() != totalInputs.size()) { - return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-size-does-not-match-outputs-size"); - } - for (const auto& outValue : totalOutputs) { - if (!totalInputs.count(outValue.first)) - return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-does-not-have-asset-that-is-in-outputs"); + if (!totalInputs.count(outValue.first)) { + std::string errorMsg; + errorMsg = strprintf("Bad Transaction - Trying to create outpoint for asset that you don't have: %s", outValue.first); + return state.DoS(100, false, REJECT_INVALID, "bad-tx-inputs-outputs-mismatch " + errorMsg); + } + + if (totalInputs.at(outValue.first) != outValue.second) { + std::string errorMsg; + errorMsg = strprintf("Bad Transaction - Assets would be burnt %s", outValue.first); + return state.DoS(100, false, REJECT_INVALID, "bad-tx-inputs-outputs-mismatch " + errorMsg); + } + } - if (totalInputs.at(outValue.first) != outValue.second) - return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-amount-mismatch-with-outputs-amount"); + // Check the input size and the output size + if (totalOutputs.size() != totalInputs.size()) { + return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-size-does-not-match-outputs-size"); } return true; diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index c80a9c78f2..530d006da8 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -10,17 +10,20 @@ #include #include +#include class CBlockIndex; class CCoinsViewCache; class CTransaction; class CValidationState; class CAssetsCache; +class CTxOut; +class uint256; /** Transaction validation functions */ /** Context-independent validity checks */ -bool CheckTransaction(const CTransaction& tx, CValidationState& state, CAssetsCache* assetCache = nullptr, bool fCheckDuplicateInputs=true, bool fMemPoolCheck=false); +bool CheckTransaction(const CTransaction& tx, CValidationState& state, CAssetsCache* assetCache = nullptr, bool fCheckDuplicateInputs=true, bool fMemPoolCheck=false, bool fCheckAssetDuplicate = true, bool fForceDuplicateCheck = true); namespace Consensus { /** @@ -32,7 +35,7 @@ namespace Consensus { bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); /** RVN START */ -bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs); +bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, std::vector >& vPairReissueAssets, const bool fRunningUnitTests = false); /** RVN END */ } // namespace Consensus diff --git a/src/consensus/validation.h b/src/consensus/validation.h index ec93dbb641..76d78708b3 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -22,6 +22,9 @@ static const unsigned char REJECT_NONSTANDARD = 0x40; // static const unsigned char REJECT_DUST = 0x41; // part of BIP 61 static const unsigned char REJECT_INSUFFICIENTFEE = 0x42; static const unsigned char REJECT_CHECKPOINT = 0x43; +/** RVN START */ +static const unsigned char REJECT_MAXREORGDEPTH = 0x44; +/** RVN END */ /** Capture information about block/transaction validation */ class CValidationState { diff --git a/src/init.cpp b/src/init.cpp index 613f92f77e..a647fa04ab 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -358,6 +358,9 @@ std::string HelpMessage(HelpMessageMode mode) if (showDebug) strUsage += HelpMessageOpt("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER)); strUsage += HelpMessageOpt("-loadblock=", _("Imports blocks from external blk000??.dat file on startup")); + strUsage += HelpMessageOpt("-maxreorg=", strprintf(_("Set the Maximum reorg depth (default: %u)"), defaultChainParams->MaxReorganizationDepth())); + strUsage += HelpMessageOpt("-minreorgpeers=", strprintf(_("Set the Minimum amount of peers required to disallow reorg of chains of depth >= maxreorg. Peers must be greater than. (default: %u)"), defaultChainParams->MinReorganizationPeers())); + strUsage += HelpMessageOpt("-minreorgage=", strprintf(_("Set the Minimum tip age (in seconds) required to allow reorg of a chain of depth >= maxreorg on a node with more than minreorgpeers peers. (default: %u)"), defaultChainParams->MinReorganizationAge())); strUsage += HelpMessageOpt("-maxorphantx=", strprintf(_("Keep at most unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS)); strUsage += HelpMessageOpt("-maxmempool=", strprintf(_("Keep the transaction memory pool below megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE)); strUsage += HelpMessageOpt("-mempoolexpiry=", strprintf(_("Do not keep transactions in the mempool longer than hours (default: %u)"), DEFAULT_MEMPOOL_EXPIRY)); @@ -500,7 +503,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-whitelistforcerelay", strprintf(_("Force relay of transactions from whitelisted peers even if they violate local relay policy (default: %d)"), DEFAULT_WHITELISTFORCERELAY)); strUsage += HelpMessageGroup(_("Block creation options:")); - strUsage += HelpMessageOpt("-blockmaxweight=", strprintf(_("Set maximum BIP141 block weight (default: %d)"), GetMaxBlockWeight() - 4000)); + strUsage += HelpMessageOpt("-blockmaxweight=", strprintf(_("Set maximum BIP141 block weight (default: %d)"), MAX_BLOCK_WEIGHT - 4000)); strUsage += HelpMessageOpt("-blockmaxsize=", _("Set maximum BIP141 block weight to this * 4. Deprecated, use blockmaxweight")); strUsage += HelpMessageOpt("-blockmintxfee=", strprintf(_("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE))); if (showDebug) @@ -1447,7 +1450,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) delete passetsCache; passetsdb = new CAssetsDB(nBlockTreeDBCache, false, fReset); passets = new CAssetsCache(); - passetsCache = new CLRUCache(MAX_CACHE_ASSETS_SIZE); + passetsCache = new CLRUCache(MAX_CACHE_ASSETS_SIZE); // Need to load assets before we verify the database @@ -1456,6 +1459,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) break; } + if (!passetsdb->ReadReissuedMempoolState()) + LogPrintf("Database failed to load last Reissued Mempool State. Will have to start from empty state"); + LogPrintf("Loaded Assets from database without error\nCache of assets size: %d\nNumber of assets I have: %d\n", passetsCache->Size(), passets->mapMyUnspentAssets.size()); if (fReset) { diff --git a/src/key.cpp b/src/key.cpp index 8d7827bd26..058f4b10c9 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -242,7 +242,7 @@ bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { return key.Derive(out.key, out.chaincode, _nChild, chaincode); } -void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) { +void CExtKey::SetSeed(const unsigned char *seed, unsigned int nSeedLen) { static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; std::vector> vout(64); CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data()); diff --git a/src/key.h b/src/key.h index 849f7b39dc..d72402c0c3 100644 --- a/src/key.h +++ b/src/key.h @@ -153,7 +153,7 @@ struct CExtKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; - void SetMaster(const unsigned char* seed, unsigned int nSeedLen); + void SetSeed(const unsigned char* seed, unsigned int nSeedLen); template void Serialize(Stream& s) const { diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 19a70c3414..cc4bad49e0 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1042,8 +1042,11 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam if (chainActive.Contains(mi->second)) { send = true; } else { + // To prevent fingerprinting attacks, only send blocks outside of the active + // chain if they are valid, and no more than a max reorg depth than the best header + // chain we know about. send = mi->second->IsValid(BLOCK_VALID_SCRIPTS) && - StaleBlockRequestAllowed(mi->second, consensusParams); + StaleBlockRequestAllowed(mi->second, consensusParams) && (chainActive.Height() - (mi->second->nHeight-1) < Params().MaxReorganizationDepth()); if (!send) { LogPrintf("%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom->GetId()); } diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index bbdef167d6..3b40bbfc5c 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -1051,3 +1051,23 @@ CAmount FeeFilterRounder::round(CAmount currentMinFee) } return static_cast(*it); } + + +int getConfTargetForIndex(int index) { + if (index+1 > static_cast(confTargets.size())) { + return confTargets.back(); + } + if (index < 0) { + return confTargets[0]; + } + return confTargets[index]; +} + +int getIndexForConfTarget(int target) { + for (unsigned int i = 0; i < confTargets.size(); i++) { + if (confTargets[i] >= target) { + return i; + } + } + return confTargets.size() - 1; +} diff --git a/src/policy/fees.h b/src/policy/fees.h index 3a7c6b0bbd..ee0c905f06 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -15,6 +15,7 @@ #include #include #include +#include class CAutoFile; class CFeeRate; @@ -295,4 +296,9 @@ class FeeFilterRounder FastRandomContext insecure_rand; }; + +static const std::array confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} }; +int getConfTargetForIndex(int index); +int getIndexForConfTarget(int target); + #endif /*RAVEN_POLICYESTIMATOR_H */ diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index a6b194b5f9..dbc985159e 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -15,6 +15,8 @@ static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; +class CCoinsViewCache; + /** An outpoint - a combination of a transaction hash and an index n into its vout */ class COutPoint { @@ -326,7 +328,11 @@ class CTransaction /** RVN START */ bool IsNewAsset() const; + bool VerifyNewAsset() const; + bool IsNewUniqueAsset() const; + bool VerifyNewUniqueAsset(CCoinsViewCache& view) const; bool IsReissueAsset() const; + bool VerifyReissueAsset(CCoinsViewCache& view) const; /** RVN END */ /** diff --git a/src/qt/assetcontroldialog.cpp b/src/qt/assetcontroldialog.cpp new file mode 100644 index 0000000000..ee739e5956 --- /dev/null +++ b/src/qt/assetcontroldialog.cpp @@ -0,0 +1,809 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroldialog.h" +#include "ui_assetcontroldialog.h" + +#include "addresstablemodel.h" +#include "ravenunits.h" +#include "guiutil.h" +#include "optionsmodel.h" +#include "platformstyle.h" +#include "txmempool.h" +#include "walletmodel.h" + +#include "wallet/coincontrol.h" +#include "init.h" +#include "policy/fees.h" +#include "policy/policy.h" +#include "validation.h" // For mempool +#include "wallet/fees.h" +#include "wallet/wallet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QList AssetControlDialog::payAmounts; +CCoinControl* AssetControlDialog::assetControl = new CCoinControl(); +bool AssetControlDialog::fSubtractFeeFromAmount = false; + +bool CAssetControlWidgetItem::operator<(const QTreeWidgetItem &other) const { + int column = treeWidget()->sortColumn(); + if (column == AssetControlDialog::COLUMN_AMOUNT || column == AssetControlDialog::COLUMN_DATE || column == AssetControlDialog::COLUMN_CONFIRMATIONS) + return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); + return QTreeWidgetItem::operator<(other); +} + +AssetControlDialog::AssetControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : + QDialog(parent), + ui(new Ui::AssetControlDialog), + model(0), + platformStyle(_platformStyle) +{ + ui->setupUi(this); + + // context menu actions + QAction *copyAddressAction = new QAction(tr("Copy address"), this); + QAction *copyLabelAction = new QAction(tr("Copy label"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this + lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this + unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this + + // context menu + contextMenu = new QMenu(this); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTransactionHashAction); + contextMenu->addSeparator(); + contextMenu->addAction(lockAction); + contextMenu->addAction(unlockAction); + + // context menu signals + connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); + connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); + connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); + + // clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); + + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); + + // toggle tree/list mode + connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); + connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(viewItemChanged(QTreeWidgetItem*, int))); + + // click on header +#if QT_VERSION < 0x050000 + ui->treeWidget->header()->setClickable(true); +#else + ui->treeWidget->header()->setSectionsClickable(true); +#endif + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // ok button + connect(ui->buttonBox, SIGNAL(clicked( QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*))); + + // (un)select all + connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + // change coin control first column label due Qt4 bug. + // see https://github.com/RavenProject/Ravencoin/issues/5716 + ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transaction hash in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT, Qt::DescendingOrder); + + // restore list mode and sortorder as a convenience feature + QSettings settings; + if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) + ui->radioTreeMode->click(); + if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) + sortView(settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); + + // Add the assets into the dropdown menu + connect(ui->viewAdministrator, SIGNAL(clicked()), this, SLOT(viewAdministratorClicked())); + connect(ui->assetList, SIGNAL(currentIndexChanged(QString)), this, SLOT(onAssetSelected(QString))); +} + +AssetControlDialog::~AssetControlDialog() +{ + QSettings settings; + settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); + settings.setValue("nCoinControlSortColumn", sortColumn); + settings.setValue("nCoinControlSortOrder", (int)sortOrder); + + delete ui; +} + +void AssetControlDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel() && _model->getAddressTableModel()) + { + updateView(); + updateAssetList(true); + updateLabelLocked(); + AssetControlDialog::updateLabels(_model, this); + } +} + +// ok button +void AssetControlDialog::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + if (AssetControlDialog::assetControl->HasAssetSelected()) + AssetControlDialog::assetControl->strAssetSelected = ui->assetList->currentText().toStdString(); + done(QDialog::Accepted); // closes the dialog + } +} + +// (un)select all +void AssetControlDialog::buttonSelectAllClicked() +{ + Qt::CheckState state = Qt::Checked; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + { + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) + { + state = Qt::Unchecked; + break; + } + } + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); + ui->treeWidget->setEnabled(true); + if (state == Qt::Unchecked) + assetControl->UnSelectAll(); // just to be sure + AssetControlDialog::updateLabels(model, this); +} + +// context menu +void AssetControlDialog::showMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeWidget->itemAt(point); + if(item) + { + contextMenuItem = item; + + // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu + if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + copyTransactionHashAction->setEnabled(true); + if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) + { + lockAction->setEnabled(false); + unlockAction->setEnabled(true); + } + else + { + lockAction->setEnabled(true); + unlockAction->setEnabled(false); + } + } + else // this means click on parent node in tree mode -> disable all + { + copyTransactionHashAction->setEnabled(false); + lockAction->setEnabled(false); + unlockAction->setEnabled(false); + } + + // show context menu + contextMenu->exec(QCursor::pos()); + } +} + +// context menu action: copy amount +void AssetControlDialog::copyAmount() +{ + GUIUtil::setClipboard(RavenUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); +} + +// context menu action: copy label +void AssetControlDialog::copyLabel() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); +} + +// context menu action: copy address +void AssetControlDialog::copyAddress() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); +} + +// context menu action: copy transaction id +void AssetControlDialog::copyTransactionHash() +{ + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); +} + +// context menu action: lock coin +void AssetControlDialog::lockCoin() +{ + if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) + contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->lockCoin(outpt); + contextMenuItem->setDisabled(true); + contextMenuItem->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + updateLabelLocked(); +} + +// context menu action: unlock coin +void AssetControlDialog::unlockCoin() +{ + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->unlockCoin(outpt); + contextMenuItem->setDisabled(false); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); + updateLabelLocked(); +} + +// copy label "Quantity" to clipboard +void AssetControlDialog::clipboardQuantity() +{ + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); +} + +// copy label "Amount" to clipboard +void AssetControlDialog::clipboardAmount() +{ + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); +} + +// copy label "Fee" to clipboard +void AssetControlDialog::clipboardFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "After fee" to clipboard +void AssetControlDialog::clipboardAfterFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "Bytes" to clipboard +void AssetControlDialog::clipboardBytes() +{ + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); +} + +// copy label "Dust" to clipboard +void AssetControlDialog::clipboardLowOutput() +{ + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); +} + +// copy label "Change" to clipboard +void AssetControlDialog::clipboardChange() +{ + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// treeview: sort +void AssetControlDialog::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); +} + +// treeview: clicked on header +void AssetControlDialog::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); + } + else + { + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); // if label or address then default => asc, else default => desc + } + + sortView(sortColumn, sortOrder); + } +} + +// toggle tree mode +void AssetControlDialog::radioTreeMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void AssetControlDialog::radioListMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void AssetControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + assetControl->UnSelectAsset(outpt); + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + else + assetControl->SelectAsset(outpt); + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + AssetControlDialog::updateLabels(model, this); + } + + // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. + // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 +#if QT_VERSION >= 0x050000 + else if (column == COLUMN_CHECKBOX && item->childCount() > 0) + { + if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } +#endif +} + +// shows count of locked unspent outputs +void AssetControlDialog::updateLabelLocked() +{ + std::vector vOutpts; + model->listLockedCoins(vOutpts); + if (vOutpts.size() > 0) + { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } + else ui->labelLocked->setVisible(false); +} + +void AssetControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +{ + if (!model) + return; + + // nPayAmount + CAmount nPayAmount = 0; + bool fDust = false; + CMutableTransaction txDummy; + for (const CAmount &amount : AssetControlDialog::payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + CTxOut txout(amount, (CScript)std::vector(24, 0)); + txDummy.vout.push_back(txout); + fDust |= IsDust(txout, ::dustRelayFee); + } + } + + std::string strAssetName = ""; + CAmount nAssetAmount = 0; + CAmount nAmount = 0; + CAmount nPayFee = 0; + CAmount nAfterFee = 0; + CAmount nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + unsigned int nQuantity = 0; + bool fWitness = false; + + std::vector vCoinControl; + std::vector vOutputs; + assetControl->ListSelectedAssets(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + for (const COutput& out : vOutputs) { + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if (model->isSpent(outpt)) + { + assetControl->UnSelectAsset(outpt); + continue; + } + + // Quantity + nQuantity++; + + // Amount + CAmount nCoinAmount; + GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nCoinAmount); + nAssetAmount += nCoinAmount; + + // Bytes + CTxDestination address; + int witnessversion = 0; + std::vector witnessprogram; + if (out.tx->tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) + { + nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); + fWitness = true; + } + else if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get(&address); + if (keyid && model->getPubKey(*keyid, pubkey)) + { + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + } + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes + nBytes = nBytesInputs + ((AssetControlDialog::payAmounts.size() > 0 ? AssetControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + if (fWitness) + { + // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. + // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. + // also, the witness stack size value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. + nBytes += 2; // account for the serialized marker and flag bytes + nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. + } + + // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate + if (AssetControlDialog::fSubtractFeeFromAmount) + if (nAmount - nPayAmount == 0) + nBytes -= 34; + + // Fee + nPayFee = GetMinimumFee(nBytes, *assetControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */); + + if (nPayAmount > 0) + { + nChange = nAssetAmount - nPayAmount; + + if (nChange == 0 && !AssetControlDialog::fSubtractFeeFromAmount) + nBytes -= 34; + } + + // after fee + nAfterFee = std::max(nPayFee, 0); + } + + // actually update labels + int nDisplayUnit = RavenUnits::RVN; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + QLabel *l1 = dialog->findChild("labelAssetControlQuantity"); + QLabel *l2 = dialog->findChild("labelAssetControlAmount"); + QLabel *l3 = dialog->findChild("labelAssetControlFee"); + QLabel *l4 = dialog->findChild("labelAssetControlAfterFee"); + QLabel *l5 = dialog->findChild("labelAssetControlBytes"); + QLabel *l7 = dialog->findChild("labelAssetControlLowOutput"); + QLabel *l8 = dialog->findChild("labelAssetControlChange"); + + // enable/disable "dust" and "change" + dialog->findChild("labelAssetControlLowOutputText")->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlLowOutput") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChangeText") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChange") ->setEnabled(nPayAmount > 0); + + // stats + l1->setText(QString::number(nQuantity)); // Quantity + l2->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nAssetAmount)); // Amount + l3->setText(RavenUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + l4->setText(RavenUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Bytes + l7->setText(fDust ? tr("yes") : tr("no")); // Dust + l8->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nChange)); // Change + if (nPayFee > 0) + { + l3->setText(ASYMP_UTF8 + l3->text()); + l4->setText(ASYMP_UTF8 + l4->text()); + } + + // turn label red when dust + l7->setStyleSheet((fDust) ? "color:red;" : ""); + + // tool tips + QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold."); + + // how many satoshis the estimated fee can vary per byte we guess wrong + double dFeeVary = (nBytes != 0) ? (double)nPayFee / nBytes : 0; + + QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); + + l3->setToolTip(toolTip4); + l4->setToolTip(toolTip4); + l7->setToolTip(toolTipDust); + l8->setToolTip(toolTip4); + dialog->findChild("labelAssetControlFeeText") ->setToolTip(l3->toolTip()); + dialog->findChild("labelAssetControlAfterFeeText") ->setToolTip(l4->toolTip()); + dialog->findChild("labelAssetControlBytesText") ->setToolTip(l5->toolTip()); + dialog->findChild("labelAssetControlLowOutputText")->setToolTip(l7->toolTip()); + dialog->findChild("labelAssetControlChangeText") ->setToolTip(l8->toolTip()); + + // Insufficient funds + QLabel *label = dialog->findChild("labelAssetControlInsuffFunds"); + if (label) + label->setVisible(nChange < 0); +} + +void AssetControlDialog::updateView() +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool treeMode = ui->radioTreeMode->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + std::map > > mapCoins; + model->listAssets(mapCoins); + + QString assetToDisplay = ui->assetList->currentText(); + + // Double check to make sure that the asset selected has coins in the map + if (!mapCoins.count(assetToDisplay)) + return; + + // For now we only support for one assets coins being shown at a time + // So we only loop through coins for that specific asset + auto mapAssetCoins = mapCoins.at(assetToDisplay); + + for (const std::pair> &coins : mapAssetCoins) { + CAssetControlWidgetItem *itemWalletAddress = new CAssetControlWidgetItem(); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + QString sWalletAddress = coins.first; + QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.isEmpty()) + sWalletLabel = tr("(no label)"); + + if (treeMode) { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + + // asset name + itemWalletAddress->setText(COLUMN_ASSET_NAME, assetToDisplay); + } + + CAmount nSum = 0; + int nChildren = 0; + for (const COutput &out : coins.second) { + std::string strAssetName; + CAmount nAmount; + if (!GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nAmount)) + continue; + + if (strAssetName != assetToDisplay.toStdString()) + continue; + + nSum += nAmount; + nChildren++; + + CAssetControlWidgetItem *itemOutput; + if (treeMode) itemOutput = new CAssetControlWidgetItem(itemWalletAddress); + else itemOutput = new CAssetControlWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) { + sAddress = QString::fromStdString(EncodeDestination(outputAddress)); + + // if listMode or change => show raven address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) { + itemOutput->setText(COLUMN_ADDRESS, sAddress); + // asset name + itemOutput->setText(COLUMN_ASSET_NAME, QString::fromStdString(strAssetName)); + } + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, + tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + } else if (!treeMode) { + QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.isEmpty()) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nAmount)); + itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, + QVariant((qlonglong) nAmount)); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); + itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong) out.tx->GetTxTime())); + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth)); + itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong) out.nDepth)); + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txhash.GetHex())); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // disable locked coins + if (model->isLockedCoin(txhash, out.i)) { + COutPoint outpt(txhash, out.i); + assetControl->UnSelectAsset(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + } + + // set checkbox + if (assetControl->IsAssetSelected(COutPoint(txhash, out.i))) + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } + + // amount + if (treeMode) { + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong) nSum)); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} + +void AssetControlDialog::viewAdministratorClicked() +{ + assetControl->UnSelectAll(); + AssetControlDialog::updateLabels(model, this); + updateAssetList(); +} + +void AssetControlDialog::updateAssetList(bool fSetOnStart) +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool showAdministrator = ui->viewAdministrator->isChecked(); + if (fSetOnStart) { + showAdministrator = IsAssetNameAnOwner(assetControl->strAssetSelected); + ui->viewAdministrator->setChecked(showAdministrator); + } + // Get the assets + std::vector assets; + if (showAdministrator) + GetAllAdministrativeAssets(model->getWallet(), assets, 0); + else + GetAllMyAssets(model->getWallet(), assets, 0); + + QStringList list; + for (auto name : assets) { + list << QString::fromStdString(name); + } + ui->assetList->clear(); + + // Add the assets into the dropdown menu + ui->assetList->addItem("Select as asset to view"); + ui->assetList->addItems(list); + + int index = ui->assetList->findText(QString::fromStdString(assetControl->strAssetSelected)); + if ( index != -1 ) { // -1 for not found + fOnStartUp = fSetOnStart; + ui->assetList->setCurrentText(QString::fromStdString(assetControl->strAssetSelected)); + } + + updateView(); +} + +void AssetControlDialog::onAssetSelected(QString name) +{ + if (fOnStartUp) { + fOnStartUp = false; + } else { + assetControl->UnSelectAll(); + } + + AssetControlDialog::updateLabels(model, this); + updateView(); +} diff --git a/src/qt/assetcontroldialog.h b/src/qt/assetcontroldialog.h new file mode 100644 index 0000000000..817f94d055 --- /dev/null +++ b/src/qt/assetcontroldialog.h @@ -0,0 +1,119 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLDIALOG_H +#define RAVEN_QT_ASSETCONTROLDIALOG_H + +#include "amount.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class PlatformStyle; +class WalletModel; + +class CCoinControl; + +namespace Ui { + class AssetControlDialog; +} + +#define ASYMP_UTF8 "\xE2\x89\x88" + +class CAssetControlWidgetItem : public QTreeWidgetItem +{ +public: + explicit CAssetControlWidgetItem(QTreeWidget *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + explicit CAssetControlWidgetItem(int type = Type) : QTreeWidgetItem(type) {} + explicit CAssetControlWidgetItem(QTreeWidgetItem *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + + bool operator<(const QTreeWidgetItem &other) const; +}; + +class AssetControlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AssetControlDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + ~AssetControlDialog(); + + void setModel(WalletModel *model); + + // static because also called from sendcoinsdialog + static void updateLabels(WalletModel*, QDialog*); + + //update the list of assets + void updateAssetList(bool fSetOnStart = false); + + static QList payAmounts; + static CCoinControl *assetControl; + static bool fSubtractFeeFromAmount; + bool fOnStartUp; + +private: + Ui::AssetControlDialog *ui; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + + QMenu *contextMenu; + QTreeWidgetItem *contextMenuItem; + QAction *copyTransactionHashAction; + QAction *lockAction; + QAction *unlockAction; + + const PlatformStyle *platformStyle; + + void sortView(int, Qt::SortOrder); + void updateView(); + + enum + { + COLUMN_CHECKBOX = 0, + COLUMN_ASSET_NAME, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + }; + friend class CAssetControlWidgetItem; + +private Q_SLOTS: + void showMenu(const QPoint &); + void copyAmount(); + void copyLabel(); + void copyAddress(); + void copyTransactionHash(); + void lockCoin(); + void unlockCoin(); + void clipboardQuantity(); + void clipboardAmount(); + void clipboardFee(); + void clipboardAfterFee(); + void clipboardBytes(); + void clipboardLowOutput(); + void clipboardChange(); + void radioTreeMode(bool); + void radioListMode(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonBoxClicked(QAbstractButton*); + void buttonSelectAllClicked(); + void updateLabelLocked(); + void viewAdministratorClicked(); + void onAssetSelected(QString name); +}; + +#endif // RAVEN_QT_ASSETCONTROLDIALOG_H diff --git a/src/qt/assetcontroltreewidget.cpp b/src/qt/assetcontroltreewidget.cpp new file mode 100644 index 0000000000..47f8a38fe2 --- /dev/null +++ b/src/qt/assetcontroltreewidget.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroltreewidget.h" +#include "assetcontroldialog.h" + +AssetControlTreeWidget::AssetControlTreeWidget(QWidget *parent) : + QTreeWidget(parent) +{ + +} + +void AssetControlTreeWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox + { + event->ignore(); + if (this->currentItem()) { + int COLUMN_CHECKBOX = 0; + this->currentItem()->setCheckState(COLUMN_CHECKBOX, ((this->currentItem()->checkState(COLUMN_CHECKBOX) == Qt::Checked) ? Qt::Unchecked : Qt::Checked)); + } + } + else if (event->key() == Qt::Key_Escape) // press esc -> close dialog + { + event->ignore(); + AssetControlDialog *assetControlDialog = (AssetControlDialog*)this->parentWidget(); + assetControlDialog->done(QDialog::Accepted); + } + else + { + this->QTreeWidget::keyPressEvent(event); + } +} diff --git a/src/qt/assetcontroltreewidget.h b/src/qt/assetcontroltreewidget.h new file mode 100644 index 0000000000..99db4e7359 --- /dev/null +++ b/src/qt/assetcontroltreewidget.h @@ -0,0 +1,23 @@ +// Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLTREEWIDGET_H +#define RAVEN_QT_ASSETCONTROLTREEWIDGET_H + +#include +#include + +class AssetControlTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + explicit AssetControlTreeWidget(QWidget *parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif // RAVEN_QT_ASSETCONTROLTREEWIDGET_H diff --git a/src/qt/assetrecord.h b/src/qt/assetrecord.h index 501e384871..5b90fe6701 100644 --- a/src/qt/assetrecord.h +++ b/src/qt/assetrecord.h @@ -18,12 +18,12 @@ class AssetRecord public: AssetRecord(): - name(""), quantity(0), units(0) + name(""), quantity(0), units(0), fIsAdministrator(false) { } - AssetRecord(const std::string _name, const CAmount& _quantity, const int _units): - name(_name), quantity(_quantity), units(_units) + AssetRecord(const std::string _name, const CAmount& _quantity, const int _units, const bool _fIsAdministrator): + name(_name), quantity(_quantity), units(_units), fIsAdministrator(_fIsAdministrator) { } @@ -47,6 +47,7 @@ class AssetRecord std::string name; CAmount quantity; int units; + bool fIsAdministrator; /**@}*/ }; diff --git a/src/qt/assetsdialog.cpp b/src/qt/assetsdialog.cpp index f881b094cd..e0a4cb3ea3 100644 --- a/src/qt/assetsdialog.cpp +++ b/src/qt/assetsdialog.cpp @@ -10,7 +10,7 @@ #include "addresstablemodel.h" #include "ravenunits.h" #include "clientmodel.h" -#include "coincontroldialog.h" +#include "assetcontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" @@ -38,25 +38,6 @@ #include #include -static const std::array confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} }; -int getConfTargetForIndexAssets(int index) { - if (index+1 > static_cast(confTargets.size())) { - return confTargets.back(); - } - if (index < 0) { - return confTargets[0]; - } - return confTargets[index]; -} -int getIndexForConfTargetAssets(int target) { - for (unsigned int i = 0; i < confTargets.size(); i++) { - if (confTargets[i] >= target) { - return i; - } - } - return confTargets.size() - 1; -} - AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::AssetsDialog), @@ -78,7 +59,7 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send")); } - GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); + GUIUtil::setupAddressWidget(ui->lineEditAssetControlChange, this); addEntry(); @@ -86,9 +67,9 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); // Coin Control - connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); - connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); - connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + connect(ui->pushButtonAssetControl, SIGNAL(clicked()), this, SLOT(assetControlButtonClicked())); + connect(ui->checkBoxAssetControlChange, SIGNAL(stateChanged(int)), this, SLOT(assetControlChangeChecked(int))); + connect(ui->lineEditAssetControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(assetControlChangeEdited(const QString &))); // Coin Control: clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); @@ -98,20 +79,20 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); - connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); - connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); - connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); - connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); - connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); - connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); - connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); - ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); - ui->labelCoinControlAmount->addAction(clipboardAmountAction); - ui->labelCoinControlFee->addAction(clipboardFeeAction); - ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); - ui->labelCoinControlBytes->addAction(clipboardBytesAction); - ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); - ui->labelCoinControlChange->addAction(clipboardChangeAction); + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardChange())); + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); // init transaction fee section QSettings settings; @@ -136,11 +117,7 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) /** RVN START */ connect(ui->createAssetButton, SIGNAL(clicked()), this, SLOT(createAssetButtonClicked())); - connect(ui->reissueAssetButton, SIGNAL(clicked()), this, SLOT(ressieAssetButtonClicked())); - connect(ui->refreshButton, SIGNAL(clicked()), this, SLOT(refreshButtonClicked())); - ui->refreshButton->setIcon(platformStyle->SingleColorIcon(":/icons/refresh")); - ui->refreshButton->setToolTip(tr("Refresh the page to display newly received assets")); - ui->optInRBF->hide(); + connect(ui->reissueAssetButton, SIGNAL(clicked()), this, SLOT(reissueAssetButtonClicked())); // If the network is regtest. Add some helper buttons to the asset GUI if (Params().NetworkIDString() != "regtest") { @@ -186,36 +163,40 @@ void AssetsDialog::setModel(WalletModel *_model) updateDisplayUnit(); // Coin Control - connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); - connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); + connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(assetControlUpdateLabels())); + connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(assetControlFeatureChanged(bool))); + + // Custom Fee Control + connect(_model->getOptionsModel(), SIGNAL(customFeeFeaturesChanged(bool)), this, SLOT(customFeeFeatureChanged(bool))); - ui->frameCoinControl->setVisible(false); - //TODO Turn on the coin control features for assets -// ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); - coinControlUpdateLabels(); + ui->frameAssetControl->setVisible(false); + ui->frameAssetControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); + ui->frameFee->setVisible(_model->getOptionsModel()->getCustomFeeFeatures()); + assetControlUpdateLabels(); // fee section for (const int &n : confTargets) { ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n)); } connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(assetControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); - connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(assetControlUpdateLabels())); + connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(assetControlUpdateLabels())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); - connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); +// connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel())); +// connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); ui->customFee->setSingleStep(GetRequiredFee(1000)); updateFeeSectionControls(); updateMinFeeLabel(); updateSmartFeeLabel(); // set default rbf checkbox state - ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked); +// ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked); + ui->optInRBF->hide(); // set the smartfee-sliders default value (wallets default conf.target or last stored value) QSettings settings; @@ -227,9 +208,9 @@ void AssetsDialog::setModel(WalletModel *_model) settings.remove("nSmartFeeSliderPosition"); } if (settings.value("nConfTarget").toInt() == 0) - ui->confTargetSelector->setCurrentIndex(getIndexForConfTargetAssets(model->getDefaultConfirmTarget())); + ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->getDefaultConfirmTarget())); else - ui->confTargetSelector->setCurrentIndex(getIndexForConfTargetAssets(settings.value("nConfTarget").toInt())); + ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt())); } } @@ -238,7 +219,7 @@ AssetsDialog::~AssetsDialog() QSettings settings; settings.setValue("fFeeSectionMinimized", fFeeMinimized); settings.setValue("nFeeRadio", ui->groupFee->checkedId()); - settings.setValue("nConfTarget", getConfTargetForIndexAssets(ui->confTargetSelector->currentIndex())); + settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex())); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); @@ -289,12 +270,19 @@ void AssetsDialog::on_sendButton_clicked() vTransfers.emplace_back(std::make_pair(CAssetTransfer(recipient.assetName.toStdString(), recipient.amount), recipient.address.toStdString())); } + // Always use a CCoinControl instance, use the AssetControlDialog instance if CoinControl has been enabled + CCoinControl ctrl; + if (model->getOptionsModel()->getCoinControlFeatures()) + ctrl = *AssetControlDialog::assetControl; + + updateAssetControlState(ctrl); + CWalletTx tx; CReserveKey reservekey(model->getWallet()); std::pair error; CAmount nFeeRequired; - if (!CreateTransferAssetTransaction(model->getWallet(), vTransfers, "", error, tx, reservekey, nFeeRequired)) { + if (!CreateTransferAssetTransaction(model->getWallet(), ctrl, vTransfers, "", error, tx, reservekey, nFeeRequired)) { QMessageBox msgBox; msgBox.setText(QString::fromStdString(error.second)); msgBox.exec(); @@ -353,12 +341,12 @@ void AssetsDialog::on_sendButton_clicked() questionString.append(" (" + QString::number((double)GetVirtualTransactionSize(tx) / 1000) + " kB)"); } - if (ui->optInRBF->isChecked()) - { - questionString.append("
"); - questionString.append(tr("This transaction signals replaceability (optin-RBF).")); - questionString.append(""); - } +// if (ui->optInRBF->isChecked()) +// { +// questionString.append("
"); +// questionString.append(tr("This transaction signals replaceability (optin-RBF).")); +// questionString.append(""); +// } SendConfirmationDialog confirmationDialog(tr("Confirm send assets"), questionString.arg(formatted.join("
")), SEND_CONFIRM_DELAY, this); @@ -379,148 +367,10 @@ void AssetsDialog::on_sendButton_clicked() if (sendStatus.status == WalletModel::OK) { accept(); - CoinControlDialog::coinControl->UnSelectAll(); - coinControlUpdateLabels(); - } - fNewRecipientAllowed = true; - - - - std::string txid; - if (!SendAssetTransaction(model->getWallet(), tx, reservekey, error, txid)) { - QMessageBox msgBox; - msgBox.setText(QString::fromStdString(error.second)); - msgBox.exec(); - return; - } else { - QMessageBox msgBox; - msgBox.setText("Transaction sent to network"); - msgBox.setInformativeText("Transaction Hash: " + QString::fromStdString(txid)); - msgBox.exec(); - accept(); + AssetControlDialog::assetControl->UnSelectAll(); + assetControlUpdateLabels(); } fNewRecipientAllowed = true; - -// // prepare transaction for getting txFee earlier -// WalletModelTransaction currentTransaction(recipients); -// WalletModel::SendCoinsReturn prepareStatus; -// -// // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled -// CCoinControl ctrl; -// if (model->getOptionsModel()->getCoinControlFeatures()) -// ctrl = *CoinControlDialog::coinControl; -// -// updateCoinControlState(ctrl); -// -// prepareStatus = model->prepareTransaction(currentTransaction, ctrl); -// -// // process prepareStatus and on error generate message shown to user -// processSendCoinsReturn(prepareStatus, -// RavenUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); -// -// if(prepareStatus.status != WalletModel::OK) { -// fNewRecipientAllowed = true; -// return; -// } -// -// CAmount txFee = currentTransaction.getTransactionFee(); -// -// // Format confirmation message -// QStringList formatted; -// for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) -// { -// // generate bold amount string -// QString amount = "" + RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); -// amount.append(""); -// // generate monospace address string -// QString address = "" + rcp.address; -// address.append(""); -// -// QString recipientElement; -// -// if (!rcp.paymentRequest.IsInitialized()) // normal payment -// { -// if(rcp.label.length() > 0) // label with address -// { -// recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); -// recipientElement.append(QString(" (%1)").arg(address)); -// } -// else // just address -// { -// recipientElement = tr("%1 to %2").arg(amount, address); -// } -// } -// else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request -// { -// recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); -// } -// else // unauthenticated payment request -// { -// recipientElement = tr("%1 to %2").arg(amount, address); -// } -// -// formatted.append(recipientElement); -// } -// -// QString questionString = tr("Are you sure you want to send?"); -// questionString.append("

%1"); -// -// if(txFee > 0) -// { -// // append fee string if a fee is required -// questionString.append("
"); -// questionString.append(RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); -// questionString.append(" "); -// questionString.append(tr("added as transaction fee")); -// -// // append transaction size -// questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)"); -// } -// -// // add total amount in all subdivision units -// questionString.append("
"); -// CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; -// QStringList alternativeUnits; -// for (RavenUnits::Unit u : RavenUnits::availableUnits()) -// { -// if(u != model->getOptionsModel()->getDisplayUnit()) -// alternativeUnits.append(RavenUnits::formatHtmlWithUnit(u, totalAmount)); -// } -// questionString.append(tr("Total Amount %1") -// .arg(RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))); -// questionString.append(QString("
(=%2)
") -// .arg(alternativeUnits.join(" " + tr("or") + "
"))); -// -// if (ui->optInRBF->isChecked()) -// { -// questionString.append("
"); -// questionString.append(tr("This transaction signals replaceability (optin-RBF).")); -// questionString.append(""); -// } -// -// SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), -// questionString.arg(formatted.join("
")), SEND_CONFIRM_DELAY, this); -// confirmationDialog.exec(); -// QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result(); -// -// if(retval != QMessageBox::Yes) -// { -// fNewRecipientAllowed = true; -// return; -// } -// -// // now send the prepared transaction -// WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); -// // process sendStatus and on error generate message shown to user -// processSendCoinsReturn(sendStatus); -// -// if (sendStatus.status == WalletModel::OK) -// { -// accept(); -// CoinControlDialog::coinControl->UnSelectAll(); -// coinControlUpdateLabels(); -// } -// fNewRecipientAllowed = true; } void AssetsDialog::clear() @@ -549,20 +399,31 @@ SendAssetsEntry *AssetsDialog::addEntry() { LOCK(cs_main); std::vector assets; - GetAllMyAssets(assets); + if (model) + GetAllMyAssets(model->getWallet(), assets, 0); + else // If the model isn't present. Grab the list of assets that the cache thinks you own + GetAllMyAssetsFromCache(assets); QStringList list; - for (auto name : assets) { - if (!IsAssetNameAnOwner(name)) - list << QString::fromStdString(name); + bool fIsOwner = false; + bool fIsAssetControl = false; + if (AssetControlDialog::assetControl->HasAssetSelected()) { + list << QString::fromStdString(AssetControlDialog::assetControl->strAssetSelected); + fIsOwner = IsAssetNameAnOwner(AssetControlDialog::assetControl->strAssetSelected); + fIsAssetControl = true; + } else { + for (auto name : assets) { + if (!IsAssetNameAnOwner(name)) + list << QString::fromStdString(name); + } } SendAssetsEntry *entry = new SendAssetsEntry(platformStyle, list, this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendAssetsEntry*)), this, SLOT(removeEntry(SendAssetsEntry*))); - connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); - connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); + connect(entry, SIGNAL(payAmountChanged()), this, SLOT(assetControlUpdateLabels())); + connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(assetControlUpdateLabels())); // Focus the field, so that entry can start immediately entry->clear(); @@ -573,14 +434,20 @@ SendAssetsEntry *AssetsDialog::addEntry() if(bar) bar->setSliderPosition(bar->maximum()); + entry->IsAssetControl(fIsAssetControl, fIsOwner); + + if (list.size() == 1) + entry->setCurrentIndex(1); + updateTabsAndLabels(); + return entry; } void AssetsDialog::updateTabsAndLabels() { setupTabChain(0); - coinControlUpdateLabels(); + assetControlUpdateLabels(); } void AssetsDialog::removeEntry(SendAssetsEntry* entry) @@ -796,7 +663,7 @@ void AssetsDialog::updateMinFeeLabel() ); } -void AssetsDialog::updateCoinControlState(CCoinControl& ctrl) +void AssetsDialog::updateAssetControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { ctrl.m_feerate = CFeeRate(ui->customFee->value()); @@ -805,8 +672,8 @@ void AssetsDialog::updateCoinControlState(CCoinControl& ctrl) } // Avoid using global defaults when sending money from the GUI // Either custom fee will be used or if not selected, the confirmation target from dropdown box - ctrl.m_confirm_target = getConfTargetForIndexAssets(ui->confTargetSelector->currentIndex()); - ctrl.signalRbf = ui->optInRBF->isChecked(); + ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex()); +// ctrl.signalRbf = ui->optInRBF->isChecked(); } void AssetsDialog::updateSmartFeeLabel() @@ -814,7 +681,7 @@ void AssetsDialog::updateSmartFeeLabel() if(!model || !model->getOptionsModel()) return; CCoinControl coin_control; - updateCoinControlState(coin_control); + updateAssetControlState(coin_control); coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels FeeCalculation feeCalc; CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc)); @@ -841,147 +708,153 @@ void AssetsDialog::updateSmartFeeLabel() } // Coin Control: copy label "Quantity" to clipboard -void AssetsDialog::coinControlClipboardQuantity() +void AssetsDialog::assetControlClipboardQuantity() { - GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); } // Coin Control: copy label "Amount" to clipboard -void AssetsDialog::coinControlClipboardAmount() +void AssetsDialog::assetControlClipboardAmount() { - GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); } // Coin Control: copy label "Fee" to clipboard -void AssetsDialog::coinControlClipboardFee() +void AssetsDialog::assetControlClipboardFee() { - GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "After fee" to clipboard -void AssetsDialog::coinControlClipboardAfterFee() +void AssetsDialog::assetControlClipboardAfterFee() { - GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Bytes" to clipboard -void AssetsDialog::coinControlClipboardBytes() +void AssetsDialog::assetControlClipboardBytes() { - GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Dust" to clipboard -void AssetsDialog::coinControlClipboardLowOutput() +void AssetsDialog::assetControlClipboardLowOutput() { - GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); } // Coin Control: copy label "Change" to clipboard -void AssetsDialog::coinControlClipboardChange() +void AssetsDialog::assetControlClipboardChange() { - GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: settings menu - coin control enabled/disabled by user -void AssetsDialog::coinControlFeatureChanged(bool checked) +void AssetsDialog::assetControlFeatureChanged(bool checked) { - ui->frameCoinControl->setVisible(checked); + ui->frameAssetControl->setVisible(checked); if (!checked && model) // coin control features disabled - CoinControlDialog::coinControl->SetNull(); + AssetControlDialog::assetControl->SetNull(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); +} + +void AssetsDialog::customFeeFeatureChanged(bool checked) +{ + ui->frameFee->setVisible(checked); } // Coin Control: button inputs -> show actual coin control dialog -void AssetsDialog::coinControlButtonClicked() +void AssetsDialog::assetControlButtonClicked() { - CoinControlDialog dlg(platformStyle); + AssetControlDialog dlg(platformStyle); dlg.setModel(model); dlg.exec(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); + assetControlUpdateSendCoinsDialog(); } // Coin Control: checkbox custom change address -void AssetsDialog::coinControlChangeChecked(int state) +void AssetsDialog::assetControlChangeChecked(int state) { if (state == Qt::Unchecked) { - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->clear(); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->clear(); } else // use this to re-validate an already entered address - coinControlChangeEdited(ui->lineEditCoinControlChange->text()); + assetControlChangeEdited(ui->lineEditAssetControlChange->text()); - ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); + ui->lineEditAssetControlChange->setEnabled((state == Qt::Checked)); } // Coin Control: custom change address changed -void AssetsDialog::coinControlChangeEdited(const QString& text) +void AssetsDialog::assetControlChangeEdited(const QString& text) { if (model && model->getAddressTableModel()) { // Default to no change address until verified - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:red;}"); const CTxDestination dest = DecodeDestination(text.toStdString()); if (text.isEmpty()) // Nothing entered { - ui->labelCoinControlChangeLabel->setText(""); + ui->labelAssetControlChangeLabel->setText(""); } else if (!IsValidDestination(dest)) // Invalid address { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Raven address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Invalid Raven address")); } else // Valid address { if (!model->IsSpendable(dest)) { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Unknown change address")); // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if(btnRetVal == QMessageBox::Yes) - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; else { - ui->lineEditCoinControlChange->setText(""); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); - ui->labelCoinControlChangeLabel->setText(""); + ui->lineEditAssetControlChange->setText(""); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setText(""); } } else // Known change address { - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); // Query label QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); if (!associatedLabel.isEmpty()) - ui->labelCoinControlChangeLabel->setText(associatedLabel); + ui->labelAssetControlChangeLabel->setText(associatedLabel); else - ui->labelCoinControlChangeLabel->setText(tr("(no label)")); + ui->labelAssetControlChangeLabel->setText(tr("(no label)")); - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; } } } } // Coin Control: update labels -void AssetsDialog::coinControlUpdateLabels() +void AssetsDialog::assetControlUpdateLabels() { if (!model || !model->getOptionsModel()) return; - updateCoinControlState(*CoinControlDialog::coinControl); + updateAssetControlState(*AssetControlDialog::assetControl); // set pay amounts - CoinControlDialog::payAmounts.clear(); - CoinControlDialog::fSubtractFeeFromAmount = false; + AssetControlDialog::payAmounts.clear(); + AssetControlDialog::fSubtractFeeFromAmount = false; for(int i = 0; i < ui->entries->count(); ++i) { @@ -989,27 +862,27 @@ void AssetsDialog::coinControlUpdateLabels() if(entry && !entry->isHidden()) { SendAssetsRecipient rcp = entry->getValue(); - CoinControlDialog::payAmounts.append(rcp.amount); + AssetControlDialog::payAmounts.append(rcp.amount); // if (rcp.fSubtractFeeFromAmount) -// CoinControlDialog::fSubtractFeeFromAmount = true; +// AssetControlDialog::fSubtractFeeFromAmount = true; } } - if (CoinControlDialog::coinControl->HasSelected()) + if (AssetControlDialog::assetControl->HasAssetSelected()) { // actual coin control calculation - CoinControlDialog::updateLabels(model, this); + AssetControlDialog::updateLabels(model, this); // show coin control stats - ui->labelCoinControlAutomaticallySelected->hide(); - ui->widgetCoinControl->show(); + ui->labelAssetControlAutomaticallySelected->hide(); + ui->widgetAssetControl->show(); } else { // hide coin control stats - ui->labelCoinControlAutomaticallySelected->show(); - ui->widgetCoinControl->hide(); - ui->labelCoinControlInsuffFunds->hide(); + ui->labelAssetControlAutomaticallySelected->show(); + ui->widgetAssetControl->hide(); + ui->labelAssetControlInsuffFunds->hide(); } } @@ -1023,11 +896,13 @@ void AssetsDialog::createAssetButtonClicked() return; } - CreateAssetDialog dlg(platformStyle, 0, model); + CreateAssetDialog dlg(platformStyle, 0, model, clientModel); + dlg.setModel(model); + dlg.setClientModel(clientModel); dlg.exec(); } -void AssetsDialog::ressieAssetButtonClicked() +void AssetsDialog::reissueAssetButtonClicked() { WalletModel::UnlockContext ctx(model->requestUnlock()); if(!ctx.isValid()) @@ -1036,24 +911,12 @@ void AssetsDialog::ressieAssetButtonClicked() return; } - ReissueAssetDialog dlg(platformStyle, 0, model); + ReissueAssetDialog dlg(platformStyle, 0, model, clientModel); + dlg.setModel(model); + dlg.setClientModel(clientModel); dlg.exec(); } -void AssetsDialog::refreshButtonClicked() -{ - for(int i = 0; i < ui->entries->count(); ++i) - { - SendAssetsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); - if(entry) - { - removeEntry(entry); - } - } - - addEntry(); -} - void AssetsDialog::mineButtonClicked() { @@ -1075,4 +938,31 @@ void AssetsDialog::mineButtonClicked() generateBlocks(coinbase_script, num_generate, max_tries, true); } + +void AssetsDialog::assetControlUpdateSendCoinsDialog() +{ + for(int i = 0; i < ui->entries->count(); ++i) + { + SendAssetsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + if(entry) + { + removeEntry(entry); + } + } + + addEntry(); + +} + +void AssetsDialog::processNewTransaction() +{ + for(int i = 0; i < ui->entries->count(); ++i) + { + SendAssetsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + if(entry) + { + entry->refreshAssetList(); + } + } +} /** RVN END */ diff --git a/src/qt/assetsdialog.h b/src/qt/assetsdialog.h index 532f74d831..e396b7c0fa 100644 --- a/src/qt/assetsdialog.h +++ b/src/qt/assetsdialog.h @@ -45,6 +45,7 @@ class AssetsDialog : public QDialog void setAddress(const QString &address); void pasteEntry(const SendAssetsRecipient &rv); bool handlePaymentRequest(const SendAssetsRecipient &recipient); + void processNewTransaction(); public Q_SLOTS: void clear(); @@ -70,7 +71,7 @@ public Q_SLOTS: void minimizeFeeSection(bool fMinimize); void updateFeeMinimizedLabel(); // Update the passed in CCoinControl with state from the GUI - void updateCoinControlState(CCoinControl& ctrl); + void updateAssetControlState(CCoinControl& ctrl); private Q_SLOTS: void on_sendButton_clicked(); @@ -78,28 +79,30 @@ private Q_SLOTS: void on_buttonMinimizeFee_clicked(); void removeEntry(SendAssetsEntry* entry); void updateDisplayUnit(); - void coinControlFeatureChanged(bool); - void coinControlButtonClicked(); - void coinControlChangeChecked(int); - void coinControlChangeEdited(const QString &); - void coinControlUpdateLabels(); - void coinControlClipboardQuantity(); - void coinControlClipboardAmount(); - void coinControlClipboardFee(); - void coinControlClipboardAfterFee(); - void coinControlClipboardBytes(); - void coinControlClipboardLowOutput(); - void coinControlClipboardChange(); + void assetControlFeatureChanged(bool); + void assetControlButtonClicked(); + void assetControlChangeChecked(int); + void assetControlChangeEdited(const QString &); + void assetControlUpdateLabels(); + void assetControlClipboardQuantity(); + void assetControlClipboardAmount(); + void assetControlClipboardFee(); + void assetControlClipboardAfterFee(); + void assetControlClipboardBytes(); + void assetControlClipboardLowOutput(); + void assetControlClipboardChange(); void setMinimumFee(); void updateFeeSectionControls(); void updateMinFeeLabel(); void updateSmartFeeLabel(); + void customFeeFeatureChanged(bool); + /** RVN START */ void createAssetButtonClicked(); - void ressieAssetButtonClicked(); - void refreshButtonClicked(); + void reissueAssetButtonClicked(); void mineButtonClicked(); + void assetControlUpdateSendCoinsDialog(); /** RVN END */ Q_SIGNALS: diff --git a/src/qt/assettablemodel.cpp b/src/qt/assettablemodel.cpp index c7117396d2..1633b448a5 100644 --- a/src/qt/assettablemodel.cpp +++ b/src/qt/assettablemodel.cpp @@ -43,20 +43,40 @@ class AssetTablePriv { qWarning("AssetTablePriv::refreshWallet: Error retrieving asset balances"); return; } - + std::set setAssetsToSkip; auto bal = balances.begin(); for (; bal != balances.end(); bal++) { // retrieve units for asset uint8_t units = OWNER_UNITS; + bool fIsAdministrator = true; + + if (setAssetsToSkip.count(bal->first)) + continue; + if (!IsAssetNameAnOwner(bal->first)) { + // Asset is not an administrator asset CNewAsset assetData; - if (!passets->GetAssetIfExists(bal->first, assetData)) { + if (!passets->GetAssetMetaDataIfExists(bal->first, assetData)) { qWarning("AssetTablePriv::refreshWallet: Error retrieving asset data"); return; } units = assetData.units; + // If we have the administrator asset, add it to the skip listå + if (balances.count(bal->first + OWNER_TAG)) { + setAssetsToSkip.insert(bal->first + OWNER_TAG); + } else { + fIsAdministrator = false; + } + } else { + // Asset is an administrator asset, if we own assets that is administrators, skip this balance + std::string name = bal->first; + name.pop_back(); + if (balances.count(name)) { + setAssetsToSkip.insert(bal->first); + continue; + } } - cachedBalances.append(AssetRecord(bal->first, bal->second, units)); + cachedBalances.append(AssetRecord(bal->first, bal->second, units, fIsAdministrator)); } } } @@ -64,16 +84,13 @@ class AssetTablePriv { int size() { - qDebug() << "AssetTablePriv::size"; return cachedBalances.size(); } AssetRecord *index(int idx) { - qDebug() << "AssetTablePriv::index(" << idx << ")"; if (idx >= 0 && idx < cachedBalances.size()) { return &cachedBalances[idx]; } - qDebug() << "AssetTablePriv::index --> 0"; return 0; } @@ -84,7 +101,6 @@ AssetTableModel::AssetTableModel(WalletModel *parent) : walletModel(parent), priv(new AssetTablePriv(this)) { - qDebug() << "AssetTableModel::AssetTableModel"; columns << tr("Name") << tr("Quantity"); priv->refreshWallet(); @@ -92,7 +108,6 @@ AssetTableModel::AssetTableModel(WalletModel *parent) : AssetTableModel::~AssetTableModel() { - qDebug() << "AssetTableModel::~AssetTableModel"; delete priv; }; @@ -107,24 +122,18 @@ void AssetTableModel::checkBalanceChanged() { int AssetTableModel::rowCount(const QModelIndex &parent) const { - qDebug() << "AssetTableModel::rowCount"; Q_UNUSED(parent); return priv->size(); } int AssetTableModel::columnCount(const QModelIndex &parent) const { - qDebug() << "AssetTableModel::columnCount"; Q_UNUSED(parent); return columns.length(); } QVariant AssetTableModel::data(const QModelIndex &index, int role) const { - if (role != Qt::DisplayRole) - return QVariant(); - - qDebug() << "AssetTableModel::data(" << index << ", " << role << ")"; Q_UNUSED(role); if(!index.isValid()) return QVariant(); @@ -133,37 +142,59 @@ QVariant AssetTableModel::data(const QModelIndex &index, int role) const switch (index.column()) { case Name: - return QString::fromStdString(rec->name); + if (role == Qt::TextAlignmentRole) { + return Qt::AlignLeft + Qt::AlignVCenter; + } else if (role == Qt::DisplayRole) { + return QString::fromStdString(rec->name); + } else if (role == Qt::DecorationRole) { + QPixmap pixmap = QPixmap::fromImage(QImage(":/icons/asset_administrator")); + return rec->fIsAdministrator ? pixmap : QVariant(); + } else if (role == Qt::SizeHintRole) { + QPixmap pixmap = QPixmap::fromImage(QImage(":/icons/asset_administrator")); + return pixmap.size(); + } else { + return QVariant(); + } case Quantity: - return QString::fromStdString(rec->formattedQuantity()); + if (role == Qt::TextAlignmentRole) { + return Qt::AlignHCenter + Qt::AlignVCenter; + } else if (role == Qt::DisplayRole) { + return QString::fromStdString(rec->formattedQuantity()); + } else { + return QVariant(); + } default: - return QString(); + return QVariant(); } } QVariant AssetTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - qDebug() << "AssetTableModel::headerData"; - if(role == Qt::DisplayRole) - { + if (role == Qt::DisplayRole) + { if (section < columns.size()) return columns.at(section); - } + } else if (role == Qt::SizeHintRole) { + if (section == 0) + return QSize(300, 50); + else if (section == 1) + return QSize(200, 50); + } else if (role == Qt::TextAlignmentRole) { + return Qt::AlignHCenter + Qt::AlignVCenter; + } return QVariant(); } QModelIndex AssetTableModel::index(int row, int column, const QModelIndex &parent) const { - qDebug() << "AssetTableModel::index(" << row << ", " << column << ", " << parent << ")"; Q_UNUSED(parent); AssetRecord *data = priv->index(row); if(data) { QModelIndex idx = createIndex(row, column, priv->index(row)); - qDebug() << "AssetTableModel::index --> " << idx; return idx; } - qDebug() << "AssetTableModel::index --> " << QModelIndex(); + return QModelIndex(); } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 0041f33683..27cd5e10f0 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -491,7 +491,6 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) } else nBytesInputs += 148; } - // calculation if (nQuantity > 0) { diff --git a/src/qt/createassetdialog.cpp b/src/qt/createassetdialog.cpp index 3bf963a30b..4e6c36f29d 100644 --- a/src/qt/createassetdialog.cpp +++ b/src/qt/createassetdialog.cpp @@ -9,26 +9,37 @@ #include "walletmodel.h" #include "addresstablemodel.h" #include "sendcoinsdialog.h" +#include "coincontroldialog.h" +#include "guiutil.h" #include "ravenunits.h" +#include "clientmodel.h" #include "optionsmodel.h" -#include -#include -#include -#include +#include "wallet/coincontrol.h" +#include "policy/fees.h" +#include "wallet/fees.h" + #include