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/CONTRIBUTING.md b/CONTRIBUTING.md index 4285f69b1a..e512f79d6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,7 +76,7 @@ the pull request affects. Valid areas as: Examples: - Consensus: Add new opcode for BIP-XXXX OP_CHECKAWESOMESIG + Consensus: Add new opcode for RIP-XXXX OP_CHECKAWESOMESIG Net: Automatically create hidden service, listen on Tor Qt: Add feed bump button Trivial: Fix typo in init.cpp @@ -193,7 +193,7 @@ In general, all pull requests must: Patches that change Raven consensus rules are considerably more involved than normal because they affect the entire ecosystem and so must be preceded by -extensive mailing list discussions and have a numbered BIP. While each case will +extensive mailing list discussions and have a numbered RIP. While each case will be different, one should be prepared to expend more time and effort than for other kinds of patches because of increased peer review and consensus building requirements. @@ -234,7 +234,7 @@ of consensus critical code. Where a patch set proposes to change the Raven consensus, it must have been discussed extensively on the mailing list and IRC, be accompanied by a widely -discussed BIP and have a generally widely perceived technical consensus of being +discussed RIP and have a generally widely perceived technical consensus of being a worthwhile change based on the judgement of the maintainers. ### Finding Reviewers 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 new file mode 100644 index 0000000000..5e52b0fa3d --- /dev/null +++ b/assets/tools/README.md @@ -0,0 +1,24 @@ +## Tools for Asset Issuance + +### Bulk Issuance +Issue assets from a .csv file. +* Make a copy of https://docs.google.com/spreadsheets/d/1Ym88-ggbw8yiMgVxOtVYDsCXJGNGZqlpOfgdbVK8iYU +* Edit your own data +* Download as .csv and put in this folder. +* ```python issuebulk.py``` + +### Signed Promises +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 new file mode 100644 index 0000000000..630fc59f11 --- /dev/null +++ b/assets/tools/issuebulk.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# Script to issue assets on the Ravencoin platform +# Reads from a csv file +# Template Google Spreadsheet at: +# https://docs.google.com/spreadsheets/d/1Ym88-ggbw8yiMgVxOtVYDsCXJGNGZqlpOfgdbVK8iYU +# In Google Sheets: File->Download As->.csv +# Prerequisite: ravend daemon to be running +# In order to use metadata, you must install be running IPFS +# Steps: +# 1. Get IPFS - https://ipfs.io/ +# 2. Run the ipfs daemon +# 3. pip install ipfsapi +# +# If you're signing contract_url +# pip install python-bitcoinrpc + + +import random +import os +import subprocess +import csv +import json +import hashlib + + +#Set this to your raven-cli program +cli = "raven-cli" + +mode = "-testnet" +rpc_port = 18766 +#mode = "-regtest" +#rpc_port = 18443 +csv_file = "Raven Assets - Sheet1.csv" +#Set this information in your raven.conf file (in datadir, not testnet3) +rpc_user = 'rpcuser' +rpc_pass = 'rpcpass555' + + +def NormalizeMetaData(dict): + #Remove all empty items + for key in list(dict.keys()): + if dict[key] == '': + del dict[key] + + del dict['asset'] + del dict['qty'] + del dict['units'] + del dict['reissuable'] + + #Convert from text to bool + if dict.get('forsale') == 'TRUE': + dict['forsale'] = True + + + return(dict) + +def issue_asset(asset, qty, units, reissuable = False, address='', ipfs_hash = ''): + cmd = cli + " " + mode + " issue " + asset + " " + str(qty) + " " + "\"" + address + "\"" + " " + "\"\"" + " " + str(units) + " " + + if reissuable: + cmd += 'true' + else: + cmd += 'false' + + if len(ipfs_hash) > 0: + cmd = cmd + " true " + ipfs_hash + + print(cmd) + os.system(cmd) + +def get_contract_hash(url): + import urllib2 + print ("Downloading: " + url) + response = urllib2.urlopen(url) + rawdata = response.read() + #print("Len: " + len(rawdata)) + hexdigest = hashlib.sha256(rawdata).hexdigest() + print(hexdigest) + return(hexdigest) + +def rpc_call(params): + process = subprocess.Popen([cli, mode, params], stdout=subprocess.PIPE) + out, err = process.communicate() + return(out) + +def get_address(): + rpc_connection = get_rpc_connection() + new_address = rpc_connection.getnewaddress() + print("New address: " + new_address) + return(new_address) + +def sign_hash(address, hash): + rpc_connection = get_rpc_connection() + signature = rpc_connection.signmessage(address, hash) + return(signature) + +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 add_to_ipfs(file): + print("Adding to IPFS") + import ipfsapi + api = ipfsapi.connect('127.0.0.1', 5001) + res = api.add(file) + print(res) + return(res['Hash']) + + + +if mode == "-regtest": #If regtest then mine our own blocks + import os + os.system(cli + " " + mode + " generate 400") + +with open(csv_file, "r") as csvfile: + #print(rpc_call('getbestblockhash')) + reader = csv.DictReader(csvfile) + for issue in reader: + if issue.get('reissuable') == 'TRUE': + issue['reissuable'] = True + else: + issue['reissuable'] = False + print(issue['asset'], issue['qty'], issue['units']) + asset = issue['asset'] + meta = issue.copy(); + NormalizeMetaData(meta) + print(issue['asset']) + + #If a contract_url is present, then sign it and add the proof to the meta data + if meta.get('contract_url'): + meta['contract_hash'] = get_contract_hash(meta['contract_url']) + meta['contract_address'] = get_address(); + meta['contract_signature'] = sign_hash(meta['contract_address'] , meta['contract_hash']) + print(meta['contract_signature']) + + + + #If there is metadata, then add to IPFS + ipfs_hash = '' + if (len(meta)): + file = issue['asset'] + '.json' + with open(file, 'w') as fp: + json.dump(meta, fp, sort_keys=True, indent=2) + ipfs_hash = add_to_ipfs(file) + print(issue['asset'] + " IPFS Hash:" + ipfs_hash) + else: + print(issue['asset'] + " No metadata") + + issue_asset(issue['asset'], issue['qty'], issue['units'], issue['reissuable'], meta.get('contract_address', ''), ipfs_hash) + + + + + diff --git a/assets/tools/signed_promises.py b/assets/tools/signed_promises.py new file mode 100644 index 0000000000..06b0f29855 --- /dev/null +++ b/assets/tools/signed_promises.py @@ -0,0 +1,197 @@ +#!/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 +# Checks the meta data for contract_url +# Downloads the documents - (named by asset) +# Checks the signature. +# In order to use metadata, you must install be running IPFS +# Steps: +# 1. Get IPFS - https://ipfs.io/ +# 2. Run the ipfs daemon +# 3. pip install ipfsapi +# +# If you're signing contract_url +# pip install python-bitcoinrpc + + +import random +import os +import subprocess +import csv +import json +import hashlib + + +cli = "raven-cli" +mode = "-testnet" +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_rpc_connection(): + from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + rpc_connection = AuthServiceProxy("http://%s:%s@127.0.0.1:18766"%(rpc_user, rpc_pass)) + return(rpc_connection) + + +def get_asset_list(filter): + rpc_connection = get_rpc_connection() + assets = rpc_connection.listassets("", True) + return(assets) + +def get_assets_with_ipfs_filter(assets): + #Build a list of assets without ipfs_hash + list_of_assets_without_ipfs = [] + for key,value in assets.iteritems(): + if (value['has_ipfs'] == 0): + list_of_assets_without_ipfs.append(key) + + #Remove all the assets without ipfs_hash + for name in list_of_assets_without_ipfs: + del assets[name] + + return(assets) + +def get_ipfs_files(assets): + list_of_bad_assets = [] + for key,value in assets.iteritems(): + if (len(value['ipfs_hash']) > 0): + success = get_ipfs_file_wget(key+'.json', value['ipfs_hash']) + if not success: + list_of_bad_assets.append(key) + #Remove all the assets without valid metadata files + for name in list_of_bad_assets: + del assets[name] + + return(assets) + + +def get_ipfs_file(filename, hash): + import ipfsapi + api = ipfsapi.connect('127.0.0.1', 5001) + + print("Downloading: " + hash + " as " + filename) + try: + api.get(hash) + except: + print("ipfs.cat failed.") + # print("Size:" + str(len(res))) + # if (len(res) < 100): + # print(res) + # return(res) + +def get_signed_assets(assets): + list_of_unsigned_assets = [] + print(assets) + for key,value in assets.iteritems(): + if key == 'RAVEN_WITH_METADATA': + print("Key " + key) + #print("Value " + value) + print("Reading metadata for " + str(key)) + metadata = read_metadata(str(key)) + print("Validating signed contract for " + key) + #print("Contract A: " + value.get('contract_url', None)) + print("Contract B: " + metadata.get('contract_url', 'Not found')) + success = validate_contract(metadata.get('contract_url', None), metadata.get('contract_hash', None), metadata.get('contract_address', None), metadata.get('contract_signature', None)) + if not success: + list_of_unsigned_assets.append(key) + #Remove all the assets without valid signatures + for name in list_of_unsigned_assets: + del assets[name] + + return(assets) + +def read_metadata(asset_name): + with open(asset_name + '.json') as handle: + metadata = json.loads(handle.read()) + return(metadata) + + +#Takes a url, hash (SHA256 of url contents), address of signer, signature of signed hash (in text) +def validate_contract(url, hash256, address, signature): + if url == None or hash256 == None or address == None or signature == None: + print("Missing info for validating contract.") + print("Requires url, hash256, address, signature") + print("url: " + url) + print("hash256: " + hash256) + print("address: " + address) + print("signature: " + signature) + return 0 + + print("Downloading: " + url + " and validating hash") + try: + filedata = urllib2.urlopen(url, timeout=30) + rawdata = filedata.read() + + hexdigest = hashlib.sha256(rawdata).hexdigest() + if (hexdigest != hash): + print("contract_url contents did not match contract_hash") + return 0 + + if not verify_message(address, signature, hash256): + print("contract_hash did not match signature") + return 0 + + except urllib2.URLError as e: + print type(e) + return 0 + except: + print("Uncaught error while downloading url") #not catch + return 0 + + print("Contract Validated") + return 1 + +def verify_message(address, signature, hash256): + rpc_connection = get_rpc_connection() + result = rpc_connection.verifymessage(address, signature, hash256) + print("Verify result: " + result) + return(result) + +def get_ipfs_file_wget(filename, hash): + import urllib2 + + print("Downloading: " + hash + " as " + filename) + try: + filedata = urllib2.urlopen('https://ipfs.io/ipfs/' + hash, timeout=20) + datatowrite = filedata.read() + + datatowrite.strip() + if (datatowrite[0] != '{'): + print("Not a valid metadata file") + return + + + with open(filename, 'wb') as f: + f.write(datatowrite) + print("Saving metadata file") + except urllib2.URLError as e: + print type(e) + return 0 + except: + print("Uncaught error while downloading") #not catch + return 0 + + return 1 + + + +def go(filter): + assets = get_asset_list(filter) + get_assets_with_ipfs_filter(assets) + #get_ipfs_files(assets) + get_signed_assets(assets) + + print(assets) + + +go("") + + + 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 ab0e8138bd..b492e106d0 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ 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, 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) 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/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index a7ec35b43e..cb3d12dc40 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -211,7 +211,8 @@ def getFrameworks(binaryPath, verbose): raise RuntimeError("otool failed with return code %d" % otool.returncode) otoolLines = o_stdout.split("\n") - otoolLines.pop(0) # First line is the inspected binary + otoolLines.append(" /usr/local/opt/boost/lib/libboost_system-mt.dylib (compatibility version 0.0.0, current version 0.0.0)") + otoolLines.pop(0) # First line is the inspected binary if ".framework" in binaryPath or binaryPath.endswith(".dylib"): otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency. 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/build-rasberrypi.md b/doc/build-rasberrypi.md new file mode 100644 index 0000000000..7c5fd63f05 --- /dev/null +++ b/doc/build-rasberrypi.md @@ -0,0 +1,44 @@ +RASBERRY PI BUILD NOTES +==================== +Origin: traysi.org/raven_rpi.php + +# Install necessary packages: +``` +sudo apt-get install git +sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils python3 +sudo apt-get install libboost-all-dev +sudo apt-get install software-properties-common +sudo apt-get update +sudo apt-get install libminiupnpc-dev +sudo apt-get install libzmq3-dev +``` + +# Increase your swap size: +``` +sudo nano /etc/dphys-swapfile +- In this file, change CONF_SWAPSIZE=100 to CONF_SWAPSIZE=1000 +sudo reboot +``` + +# Build Berkeley DB 4.8: +``` +cd ~ +mkdir build +cd build +wget http://download.oracle.com/berkeley-db/db-4.8.30.NC.tar.gz +tar -xzvf db-4.8.30.NC.tar.gz +cd db-4.8.30.NC/build_unix/ +../dist/configure --enable-cxx +make -j4 # If error, remove the -j4 +sudo make install +``` + +# Build Ravencoin +``` +cd ~/build/ +git clone https://github.com/RavenProject/Ravencoin +cd Ravencoin/ +./autogen.sh +./configure --disable-tests --with-gui=no CPPFLAGS="-I/usr/local/BerkeleyDB.4.8/include -O2" LDFLAGS="-L/usr/local/BerkeleyDB.4.8/lib" +make +``` 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/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/roadmap/README.md b/roadmap/README.md index fb9740d8b3..985ffc2d8d 100644 --- a/roadmap/README.md +++ b/roadmap/README.md @@ -6,7 +6,7 @@ Ravencoin (RVN) is a Proof of Work coin built on the Bitcoin UTXO model. As with * x1000 coin distribution (21 Billion Total) * 10x faster blocks (1 per minute) * In app CPU mining -* ~1.4 Day difficulty adjustment (2016 blocks) +* Dark Gravity Wave difficulty adjustment (180 block average) * Addresses start with R... for regular addresses, or r... for multisig * Network Port: 8767 * RPC Port: 8766 @@ -15,7 +15,7 @@ Ravencoin (RVN) is a Proof of Work coin built on the Bitcoin UTXO model. As with #### ASIC Resistance -ASIC Resistance - A published commitment to continual attempts at ASIC resistance. If ASICs are created for x16r, then we will, at a specific block number, modify one of the algorithms to add Equihash, EthHash or similar efforts to increase the resistance to ASIC miners for Raven. +ASIC Resistance - A published commitment to continual attempts at ASIC resistance. If ASICs are created for x16r, then we will, at a specific block number, modify one of the algorithms to add some varients of Equihash or similar efforts to increase the resistance to ASIC miners for Raven. #### Asset Support @@ -32,7 +32,8 @@ THEEND. A..B (consecutive punctuation) AB 12 -.FIRST +.FIRST +apple The RVN used to issue assets will be sent to a burn address, which will reduce the amount of RVN available. 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 5164ef07d1..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,14 +187,9 @@ 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) { - // flush everything to db - LOCK(cs_main); - bool flushed = pcoinsTip->Flush(); - assert(flushed); - bool assetFlushed = passets->Flush(false, true); - assert(assetFlushed); + FlushStateToDisk(); std::unique_ptr pcursor(NewIterator()); pcursor->Seek(std::make_pair(ASSET_FLAG, std::string())); @@ -216,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__); @@ -234,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 5a68bf7384..4013d5f0c4 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -26,15 +26,22 @@ #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._]+$"); static const std::regex DOUBLE_PUNCTUATION("^.*[._]{2,}.*$"); static const std::regex LEADING_PUNCTUATION("^[._].*$"); @@ -43,12 +50,14 @@ static const std::regex TRAILING_PUNCTUATION("^.*[._]$"); static const std::string SUB_NAME_DELIMITER = "/"; static const std::string UNIQUE_TAG_DELIMITER = "#"; static const std::string CHANNEL_TAG_DELIMITER = "~"; +static const std::string VOTE_TAG_DELIMITER = "^"; -static const std::regex UNIQUE_INDICATOR("^[^#]+#[^#]+$"); -static const std::regex CHANNEL_INDICATOR("^[^~]+~[^~]+$"); -static const std::regex OWNER_INDICATOR("^[^!]+!$"); +static const std::regex UNIQUE_INDICATOR(R"(^[^^~#!]+#[^~#!\/]+$)"); +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) { @@ -72,6 +81,11 @@ bool IsUniqueTagValid(const std::string& tag) return std::regex_match(tag, UNIQUE_TAG_CHARACTERS); } +bool IsVoteTagValid(const std::string& tag) +{ + return std::regex_match(tag, VOTE_TAG_CHARACTERS); +} + bool IsChannelTagValid(const std::string& tag) { return std::regex_match(tag, CHANNEL_TAG_CHARACTERS) @@ -108,51 +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::CHANNEL; - 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; + bool ret = IsTypeCheckNameValid(AssetType::OWNER, name, error); + if (ret) + assetType = AssetType::OWNER; + + return ret; + } + else if (std::regex_match(name, VOTE_INDICATOR)) + { + 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) @@ -160,57 +186,174 @@ 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; + if (!IsAssetNameValid(name, type)) + return ""; + + auto index = std::string::npos; + if (type == AssetType::SUB) { + index = name.find_last_of(SUB_NAME_DELIMITER); + } else if (type == AssetType::UNIQUE) { + index = name.find_last_of(UNIQUE_TAG_DELIMITER); + } else if (type == AssetType::MSGCHANNEL) { + index = name.find_last_of(CHANNEL_TAG_DELIMITER); + } else if (type == AssetType::VOTE) { + index = name.find_last_of(VOTE_TAG_DELIMITER); + } else if (type == AssetType::ROOT) + return name; + + if (std::string::npos != index) + { + return name.substr(0, index); + } + + 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 (nAmount <= 0) - strError = "Invalid parameter: asset amount can't be equal to or less than zero."; + 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 (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 (nAmount > MAX_MONEY) { + strError = _("Invalid parameter: asset amount greater than max money: ") + std::to_string(MAX_MONEY / COIN); + return false; + } - if (units < 0 || units > 8) - strError = "Invalid parameter: units must be between 0-8."; + if (units < 0 || units > 8) { + strError = _("Invalid parameter: units must be between 0-8."); + return false; + } - if (nReissuable != 0 && nReissuable != 1) - strError = "Invalid parameter: reissuable 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 != 0 && nHasIPFS != 1) - strError = "Invalid parameter: has_ipfs must be 0 or 1."; + if (nReissuable != 0 && nReissuable != 1) { + strError = _("Invalid parameter: reissuable must be 0 or 1"); + return false; + } - if (nHasIPFS && strIPFSHash.size() != 34) - strError = "Invalid parameter: ipfs_hash must be 34 bytes."; + if (nHasIPFS != 0 && nHasIPFS != 1) { + strError = _("Invalid parameter: has_ipfs must be 0 or 1."); + return false; + } - return strError == ""; + 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) @@ -260,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 @@ -278,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 @@ -293,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) @@ -320,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 @@ -364,7 +542,8 @@ bool OwnerFromTransaction(const CTransaction& tx, std::string& ownerName, std::s bool TransferAssetFromScript(const CScript& scriptPubKey, CAssetTransfer& assetTransfer, std::string& strAddress) { - if (!IsScriptTransferAsset(scriptPubKey)) + int nStartingIndex = 0; + if (!IsScriptTransferAsset(scriptPubKey, nStartingIndex)) return false; CTxDestination destination; @@ -388,7 +567,8 @@ bool TransferAssetFromScript(const CScript& scriptPubKey, CAssetTransfer& assetT bool AssetFromScript(const CScript& scriptPubKey, CNewAsset& assetNew, std::string& strAddress) { - if (!IsScriptNewAsset(scriptPubKey)) + int nStartingIndex = 0; + if (!IsScriptNewAsset(scriptPubKey, nStartingIndex)) return false; CTxDestination destination; @@ -397,7 +577,7 @@ bool AssetFromScript(const CScript& scriptPubKey, CNewAsset& assetNew, std::stri strAddress = EncodeDestination(destination); std::vector vchNewAsset; - vchNewAsset.insert(vchNewAsset.end(), scriptPubKey.begin() + 31, scriptPubKey.end()); + vchNewAsset.insert(vchNewAsset.end(), scriptPubKey.begin() + nStartingIndex, scriptPubKey.end()); CDataStream ssAsset(vchNewAsset, SER_NETWORK, PROTOCOL_VERSION); try { @@ -412,7 +592,8 @@ bool AssetFromScript(const CScript& scriptPubKey, CNewAsset& assetNew, std::stri bool OwnerAssetFromScript(const CScript& scriptPubKey, std::string& assetName, std::string& strAddress) { - if (!IsScriptOwnerAsset(scriptPubKey)) + int nStartingIndex = 0; + if (!IsScriptOwnerAsset(scriptPubKey, nStartingIndex)) return false; CTxDestination destination; @@ -421,7 +602,7 @@ bool OwnerAssetFromScript(const CScript& scriptPubKey, std::string& assetName, s strAddress = EncodeDestination(destination); std::vector vchOwnerAsset; - vchOwnerAsset.insert(vchOwnerAsset.end(), scriptPubKey.begin() + 31, scriptPubKey.end()); + vchOwnerAsset.insert(vchOwnerAsset.end(), scriptPubKey.begin() + nStartingIndex, scriptPubKey.end()); CDataStream ssOwner(vchOwnerAsset, SER_NETWORK, PROTOCOL_VERSION); try { @@ -436,7 +617,8 @@ bool OwnerAssetFromScript(const CScript& scriptPubKey, std::string& assetName, s bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, std::string& strAddress) { - if (!IsScriptReissueAsset(scriptPubKey)) + int nStartingIndex = 0; + if (!IsScriptReissueAsset(scriptPubKey, nStartingIndex)) return false; CTxDestination destination; @@ -444,9 +626,9 @@ bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, strAddress = EncodeDestination(destination); - std::vector vchTransferAsset; - vchTransferAsset.insert(vchTransferAsset.end(), scriptPubKey.begin() + 31, scriptPubKey.end()); - CDataStream ssReissue(vchTransferAsset, SER_NETWORK, PROTOCOL_VERSION); + std::vector vchReissueAsset; + vchReissueAsset.insert(vchReissueAsset.end(), scriptPubKey.begin() + nStartingIndex, scriptPubKey.end()); + CDataStream ssReissue(vchReissueAsset, SER_NETWORK, PROTOCOL_VERSION); try { ssReissue >> reissue; @@ -458,12 +640,41 @@ bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, return true; } +//! Call VerifyNewAsset if this function returns true bool CTransaction::IsNewAsset() const { - // Reissuing 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; @@ -472,15 +683,144 @@ bool CTransaction::IsNewAsset() const if (!CheckOwnerDataTx(vout[vout.size() - 2])) return false; + // Get the asset type + CNewAsset asset; + std::string address; + if (!AssetFromScript(vout[vout.size() - 1].scriptPubKey, asset, address)) + return error("%s : Failed to get new asset from transaction: %s", __func__, this->GetHash().GetHex()); + + 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)) + if (CheckIssueBurnTx(out, assetType)) return true; 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) @@ -491,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; - if (!ownerFound) + 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 (!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 @@ -506,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; } @@ -540,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) { @@ -551,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 @@ -558,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 exsist 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; } @@ -605,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) @@ -652,7 +1050,10 @@ void CAssetsCache::AddToAssetBalance(const std::string& strName, const std::stri mapAssetsAddressAmount.insert(make_pair(pair, 0)); // Add the new amount to the balance - mapAssetsAddressAmount.at(pair) += nAmount; + if (IsAssetNameAnOwner(strName)) + mapAssetsAddressAmount.at(pair) = OWNER_ASSET_AMOUNT; + else + mapAssetsAddressAmount.at(pair) += nAmount; // Add to map of addresses if (!mapAssetsAddresses.count(strName)) { @@ -663,78 +1064,79 @@ void CAssetsCache::AddToAssetBalance(const std::string& strName, const std::stri bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) { - if (vpwallets.size() == 0) { CAssetCachePossibleMine possibleMine("", out, txOut); setPossiblyMineRemove.insert(possibleMine); return true; } - std::pair pairToRemove = std::make_pair("", COutPoint()); - for (auto setOuts : mapMyUnspentAssets) { - // If we own one of the assets, we need to update our databases and memory - if (setOuts.second.count(out)) { - - // Placeholder strings that will get set if you successfully get the transfer or asset from the script - std::string address = ""; - std::string assetName = ""; - CAmount nAmount = -1; - - // Get the New Asset or Transfer Asset from the scriptPubKey - if (txOut.scriptPubKey.IsNewAsset()) { - CNewAsset asset; - if (AssetFromScript(txOut.scriptPubKey, asset, address)) { - assetName = asset.strName; - nAmount = asset.nAmount; - } - } else if (txOut.scriptPubKey.IsTransferAsset()) { - CAssetTransfer transfer; - if (TransferAssetFromScript(txOut.scriptPubKey, transfer, address)) { - assetName = transfer.strName; - nAmount = transfer.nAmount; - } - } else if (txOut.scriptPubKey.IsOwnerAsset()) { - 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 (txOut.scriptPubKey.IsReissueAsset()) { - CReissueAsset reissue; - if (ReissueAssetFromScript(txOut.scriptPubKey, reissue, address)) { - assetName = reissue.strName; - nAmount = reissue.nAmount; - } + // Placeholder strings that will get set if you successfully get the transfer or asset from the script + std::string address = ""; + std::string assetName = ""; + CAmount nAmount = -1; + + // Get the asset tx data + int nType = -1; + bool fIsOwner = false; + if (txOut.scriptPubKey.IsAssetScript(nType, fIsOwner)) { + + // Get the New Asset or Transfer Asset from the scriptPubKey + if (nType == TX_NEW_ASSET && !fIsOwner) { + CNewAsset asset; + if (AssetFromScript(txOut.scriptPubKey, asset, address)) { + assetName = asset.strName; + nAmount = asset.nAmount; } - - // If we got the address and the assetName, proceed to remove it from the database, and in memory objects - if (address != "" && assetName != "" && nAmount > 0) { - CAssetCacheSpendAsset spend(assetName, address, nAmount); - if (mapAssetsAddressAmount.count(make_pair(assetName, address))) { - assert(mapAssetsAddressAmount[make_pair(assetName, address)] >= nAmount); - mapAssetsAddressAmount[make_pair(assetName, address)] -= nAmount; - pairToRemove = std::make_pair(assetName, out); - - if (mapAssetsAddressAmount[make_pair(assetName, address)] == 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 : , Address : ", __func__, assetName, address); - } - - } else { - return error("%s : ERROR Failed to get asset from the OutPoint: %s", __func__, out.ToString()); + } else if (nType == TX_TRANSFER_ASSET) { + CAssetTransfer transfer; + if (TransferAssetFromScript(txOut.scriptPubKey, transfer, address)) { + assetName = transfer.strName; + nAmount = transfer.nAmount; + } + } 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 (nType == TX_REISSUE_ASSET) { + CReissueAsset reissue; + if (ReissueAssetFromScript(txOut.scriptPubKey, reissue, address)) { + assetName = reissue.strName; + nAmount = reissue.nAmount; } - break; } + } else { + // If it isn't an asset tx return true, we only fail if an error occurs + return true; } - if (pairToRemove.first != "" && !pairToRemove.second.IsNull() && mapMyUnspentAssets.count(pairToRemove.first)) { - mapMyUnspentAssets.at(pairToRemove.first).erase(pairToRemove.second); + // If we got the address and the assetName, proceed to remove it from the database, and in memory objects + if (address != "" && assetName != "" && nAmount > 0) { + CAssetCacheSpendAsset spend(assetName, address, nAmount); + if (GetBestAssetAddressAmount(*this, assetName, address)) { + auto pair = make_pair(assetName, address); + mapAssetsAddressAmount.at(pair) -= nAmount; + + 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 know which assets set of outpoint we need to save to database - setChangeOwnedOutPoints.insert(pairToRemove.first); + // Update the cache so we can save to database + vSpentAssets.push_back(spend); + } + } else { + return error("%s : ERROR Failed to get asset from the OutPoint: %s", __func__, out.ToString()); + } + // If we own one of the assets, we need to update our databases and memory + if (mapMyUnspentAssets.count(assetName)) { + if (mapMyUnspentAssets.at(assetName).count(out)) { + mapMyUnspentAssets.at(assetName).erase(out); + + // Update the cache so we know which assets set of outpoint we need to save to database + setChangeOwnedOutPoints.insert(assetName); + } } return true; @@ -748,7 +1150,7 @@ bool CAssetsCache::AddToMyUpspentOutPoints(const std::string& strName, const COu mapMyUnspentAssets.insert(std::make_pair(strName, setOuts)); } else { if (!mapMyUnspentAssets[strName].insert(out).second) - return error("%s: Tried adding an asset to my map of upspent assets, but it already exsisted in the set of assets: %s, COutPoint: %s", __func__, strName, out.ToString()); + return error("%s: Tried adding an asset to my map of upspent assets, but it already existed in the set of assets: %s, COutPoint: %s", __func__, strName, out.ToString()); } // Add the outpoint to the set so we know what we need to database @@ -791,36 +1193,47 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) CAmount nAmount = 0; // Get the asset tx from the script - if (IsScriptNewAsset(coin.out.scriptPubKey)) { - 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", - __func__, - out.ToString()); - } - assetName = asset.strName; + int nType = -1; + bool fIsOwner = false; + if(coin.out.scriptPubKey.IsAssetScript(nType, 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", + __func__, + out.ToString()); + } + assetName = asset.strName; - nAmount = asset.nAmount; - } else if (IsScriptTransferAsset(coin.out.scriptPubKey)) { - CAssetTransfer transfer; - if (!TransferAssetFromScript(coin.out.scriptPubKey, transfer, strAddress)) - return error("%s : Failed to get transfer asset from script while trying to undo asset spend. OutPoint : %s", __func__, - out.ToString()); - - assetName = transfer.strName; - nAmount = transfer.nAmount; - } else if (IsScriptOwnerAsset(coin.out.scriptPubKey)) { - std::string ownerName; - if (!OwnerAssetFromScript(coin.out.scriptPubKey, ownerName, strAddress)) - return error("%s : Failed to get owner asset from script while trying to undo asset spend. OutPoint : %s", __func__, out.ToString()); - assetName = ownerName; - nAmount = OWNER_ASSET_AMOUNT; - } else if (IsScriptReissueAsset(coin.out.scriptPubKey)) { - CReissueAsset reissue; - if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, strAddress)) - return error("%s : Failed to get reissue asset from script while trying to undo asset spend. OutPoint : %s", __func__, out.ToString()); - assetName = reissue.strName; - nAmount = reissue.nAmount; + nAmount = asset.nAmount; + } else if (nType == TX_TRANSFER_ASSET) { + CAssetTransfer transfer; + if (!TransferAssetFromScript(coin.out.scriptPubKey, transfer, strAddress)) + return error( + "%s : Failed to get transfer asset from script while trying to undo asset spend. OutPoint : %s", + __func__, + out.ToString()); + + assetName = transfer.strName; + nAmount = transfer.nAmount; + } else if (nType == TX_NEW_ASSET && fIsOwner) { + std::string ownerName; + if (!OwnerAssetFromScript(coin.out.scriptPubKey, ownerName, strAddress)) + return error( + "%s : Failed to get owner asset from script while trying to undo asset spend. OutPoint : %s", + __func__, out.ToString()); + assetName = ownerName; + nAmount = OWNER_ASSET_AMOUNT; + } else if (nType == TX_REISSUE_ASSET) { + CReissueAsset reissue; + if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, strAddress)) + return error( + "%s : Failed to get reissue asset from script while trying to undo asset spend. OutPoint : %s", + __func__, out.ToString()); + assetName = reissue.strName; + nAmount = reissue.nAmount; + } } if (assetName == "" || strAddress == "" || nAmount == 0) @@ -916,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); @@ -927,15 +1340,15 @@ 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 exsisted in the set of assets: %s", __func__, asset.strName); + return error("%s: Tried adding new asset, but it already existed in the set of assets: %s", __func__, asset.strName); // Insert the asset into the assets address map if (mapAssetsAddresses.count(asset.strName)) { if (mapAssetsAddresses[asset.strName].count(address)) - return error("%s : Tried adding a new asset and saving its quantity, but it already exsisted in the map of assets addresses: %s", __func__, asset.strName); + return error("%s : Tried adding a new asset and saving its quantity, but it already existed in the map of assets addresses: %s", __func__, asset.strName); mapAssetsAddresses[asset.strName].insert(address); } else { @@ -947,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); @@ -962,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)) { @@ -985,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; @@ -1001,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); @@ -1012,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 @@ -1047,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; @@ -1059,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); @@ -1074,7 +1497,7 @@ bool CAssetsCache::AddOwnerAsset(const std::string& assetsName, const std::strin { if (mapAssetsAddresses.count(assetsName)) { if (mapAssetsAddresses[assetsName].count(address)) - return error("%s : Tried adding an owner asset, but it already exsisted in the map of assets addresses: %s", + return error("%s : Tried adding an owner asset, but it already existed in the map of assets addresses: %s", __func__, assetsName); mapAssetsAddresses[assetsName].insert(address); @@ -1148,26 +1571,8 @@ 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); if (!passetsdb->EraseAssetData(newAsset.asset.strName)) { dirty = true; @@ -1191,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"; } @@ -1214,7 +1619,6 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) // Remove the new owners from database for (auto ownerAsset : setNewOwnerAssetsToRemove) { - if (!passetsdb->EraseAssetAddressQuantity(ownerAsset.assetName, ownerAsset.address)) { dirty = true; message = "_Failed Erasing Owner Address Balance from database"; @@ -1227,15 +1631,17 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) // Add the new owners to database for (auto ownerAsset : setNewOwnerAssetsToAdd) { + auto pair = std::make_pair(ownerAsset.assetName, ownerAsset.address); + if (mapAssetsAddressAmount.count(pair) && mapAssetsAddressAmount.at(pair) > 0) { + if (!passetsdb->WriteAssetAddressQuantity(ownerAsset.assetName, ownerAsset.address, + mapAssetsAddressAmount.at(pair))) { + dirty = true; + message = "_Failed Writing Owner Address Balance to database"; + } - if (!passetsdb->WriteAssetAddressQuantity(ownerAsset.assetName, ownerAsset.address, - OWNER_ASSET_AMOUNT)) { - dirty = true; - message = "_Failed Writing Owner Address Balance to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); + if (dirty) { + return error("%s : %s", __func__, message); + } } } @@ -1288,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"; } @@ -1301,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"; } @@ -1314,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"; } @@ -1374,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(); } @@ -1419,10 +1860,29 @@ bool IsAssetUnitsValid(const CAmount& units) return false; } -bool CheckIssueBurnTx(const CTxOut& txOut) +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued) { - // Check the first transaction is the 500 Burn amount to the burn address - if (txOut.nValue != GetIssueAssetBurnAmount()) + CAmount burnAmount = 0; + std::string burnAddress = ""; + + if (type == AssetType::SUB) { + burnAmount = GetIssueSubAssetBurnAmount(); + burnAddress = Params().IssueSubAssetBurnAddress(); + } else if (type == AssetType::ROOT) { + burnAmount = GetIssueAssetBurnAmount(); + burnAddress = Params().IssueAssetBurnAddress(); + } else if (type == AssetType::UNIQUE) { + burnAmount = GetIssueUniqueAssetBurnAmount(); + burnAddress = Params().IssueUniqueAssetBurnAddress(); + } else { + 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; // Extract the destination @@ -1435,12 +1895,18 @@ bool CheckIssueBurnTx(const CTxOut& txOut) return false; // Check destination address is the burn address - if (EncodeDestination(destination) != Params().IssueAssetBurnAddress()) + auto strDestination = EncodeDestination(destination); + if (!(strDestination == burnAddress)) return false; 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 @@ -1468,7 +1934,8 @@ bool CheckIssueDataTx(const CTxOut& txOut) // Verify 'rvnq' is in the transaction CScript scriptPubKey = txOut.scriptPubKey; - return IsScriptNewAsset(scriptPubKey); + int nStartingIndex = 0; + return IsScriptNewAsset(scriptPubKey, nStartingIndex); } bool CheckReissueDataTx(const CTxOut& txOut) @@ -1497,21 +1964,58 @@ bool CheckTransferOwnerTx(const CTxOut& txOut) bool IsScriptNewAsset(const CScript& scriptPubKey) { - if (scriptPubKey.size() > 39) { - if (scriptPubKey[25] == OP_RVN_ASSET && scriptPubKey[27] == RVN_R && scriptPubKey[28] == RVN_V && scriptPubKey[29] == RVN_N && scriptPubKey[30] == RVN_Q) { - return true; - } - } + int index = 0; + return IsScriptNewAsset(scriptPubKey, index); +} +bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner =false; + if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { + 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) { - if (scriptPubKey.size() > 30) { - if (scriptPubKey[25] == OP_RVN_ASSET && scriptPubKey[27] == RVN_R && scriptPubKey[28] == RVN_V && scriptPubKey[29] == RVN_N && scriptPubKey[30] == RVN_O) { - return true; - } + + int index = 0; + return IsScriptOwnerAsset(scriptPubKey, index); +} + +bool IsScriptOwnerAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner =false; + if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { + return nType == TX_NEW_ASSET && fIsOwner; } return false; @@ -1519,10 +2023,16 @@ bool IsScriptOwnerAsset(const CScript& scriptPubKey) bool IsScriptReissueAsset(const CScript& scriptPubKey) { - if (scriptPubKey.size() > 39) { - if (scriptPubKey[25] == OP_RVN_ASSET && scriptPubKey[27] == RVN_R && scriptPubKey[28] == RVN_V && scriptPubKey[29] == RVN_N && scriptPubKey[30] == RVN_R) { - return true; - } + int index = 0; + return IsScriptReissueAsset(scriptPubKey, index); +} + +bool IsScriptReissueAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner =false; + if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { + return nType == TX_REISSUE_ASSET; } return false; @@ -1530,10 +2040,16 @@ bool IsScriptReissueAsset(const CScript& scriptPubKey) bool IsScriptTransferAsset(const CScript& scriptPubKey) { - if (scriptPubKey.size() > 30) { - if (scriptPubKey[25] == OP_RVN_ASSET && scriptPubKey[27] == RVN_R && scriptPubKey[28] == RVN_V && scriptPubKey[29] == RVN_N && scriptPubKey[30] == RVN_T) { - return true; - } + int index = 0; + return IsScriptTransferAsset(scriptPubKey, index); +} + +bool IsScriptTransferAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner =false; + if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { + return nType == TX_TRANSFER_ASSET; } return false; @@ -1577,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); + } } } } @@ -1611,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)) { @@ -1622,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)) { @@ -1632,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; } } @@ -1655,116 +2203,116 @@ 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) { - if (!coin.IsAsset()) + CAssetOutputEntry data; + if(!GetAssetData(scriptPubKey, data)) return false; - // Determine the type of asset that the scriptpubkey contains and return the name and amount - if (coin.out.scriptPubKey.IsNewAsset()) { - CNewAsset asset; - std::string address; - if (!AssetFromScript(coin.out.scriptPubKey, asset, address)) - return false; - strName = asset.strName; - nAmount = asset.nAmount; - return true; - } else if (coin.out.scriptPubKey.IsTransferAsset()) { - CAssetTransfer asset; - std::string address; - if (!TransferAssetFromScript(coin.out.scriptPubKey, asset, address)) - return false; - strName = asset.strName; - nAmount = asset.nAmount; - return true; - } else if (coin.out.scriptPubKey.IsOwnerAsset()) { - 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 (coin.out.scriptPubKey.IsReissueAsset()) { - CReissueAsset reissue; - std::string address; - if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, address)) - return false; - strName = reissue.strName; - nAmount = reissue.nAmount; - return true; - } + strName = data.assetName; + nAmount = data.nAmount; - return false; + return true; +} + +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 false; + } + + txnouttype type = txnouttype(nType); + // Get the New Asset or Transfer Asset from the scriptPubKey - if (script.IsNewAsset()) { + 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 (script.IsTransferAsset()) { + } 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 (script.IsOwnerAsset()) { + } 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 (script.IsReissueAsset()) { + } 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() @@ -1787,6 +2335,60 @@ CAmount GetIssueUniqueAssetBurnAmount() return Params().IssueUniqueAssetBurnAmount(); } +CAmount GetBurnAmount(const int nType) +{ + return GetBurnAmount((AssetType(nType))); +} + +CAmount GetBurnAmount(const AssetType type) +{ + switch (type) { + case AssetType::ROOT: + return GetIssueAssetBurnAmount(); + case AssetType::SUB: + return GetIssueSubAssetBurnAmount(); + case AssetType::MSGCHANNEL: + return 0; + case AssetType::OWNER: + return 0; + case AssetType::UNIQUE: + return GetIssueUniqueAssetBurnAmount(); + case AssetType::VOTE: + return 0; + case AssetType::REISSUE: + return GetReissueAssetBurnAmount(); + default: + return 0; + } +} + +std::string GetBurnAddress(const int nType) +{ + return GetBurnAddress((AssetType(nType))); +} + +std::string GetBurnAddress(const AssetType type) +{ + switch (type) { + case AssetType::ROOT: + return Params().IssueAssetBurnAddress(); + case AssetType::SUB: + return Params().IssueSubAssetBurnAddress(); + case AssetType::MSGCHANNEL: + return ""; + case AssetType::OWNER: + return ""; + case AssetType::UNIQUE: + return Params().IssueUniqueAssetBurnAddress(); + case AssetType::VOTE: + return ""; + case AssetType::REISSUE: + return Params().ReissueAssetBurnAddress(); + default: + return ""; + } +} + //! This will get the amount that an address for a certain asset contains from the database if they cache doesn't already have it bool GetBestAssetAddressAmount(CAssetsCache& cache, const std::string& assetName, const std::string& address) { @@ -1889,28 +2491,70 @@ 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); +} + +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; + } } - bool haveChangeAddress = false; - if (rvnChangeAddress != "") - haveChangeAddress = true; + if (!change_address.empty()) { + CTxDestination destination = DecodeDestination(change_address); + if (!IsValidDestination(destination)) { + error = std::make_pair(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Raven address: ") + change_address); + return false; + } + } else { + // 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; + } - if (haveChangeAddress && !IsValidDestinationString(rvnChangeAddress)) { - error = std::make_pair(RPC_INVALID_ADDRESS_OR_KEY, "Change Address isn't a valid RVN address"); - return false; + change_address = EncodeDestination(keyID); + coinControl.destChange = DecodeDestination(change_address); } - CAmount curBalance = pwallet->GetBalance(); + AssetType assetType; + 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; + } + } - // Get the current burn amount for issuing an asset - CAmount burnAmount = GetIssueAssetBurnAmount(); + // Assign the correct burn amount and the correct burn address depending on the type of asset issuance that is happening + CAmount burnAmount = GetBurnAmount(assetType) * assets.size(); + CScript scriptPubKey = GetScriptForDestination(DecodeDestination(GetBurnAddress(assetType))); + + CAmount curBalance = pwallet->GetBalance(); // Check to make sure the wallet has the RVN required by the burnAmount if (curBalance < burnAmount) { @@ -1923,16 +2567,6 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: return false; } - // Get the script for the burn address - CScript scriptPubKey = GetScriptForDestination(DecodeDestination(Params().IssueAssetBurnAddress())); - - CCoinControl coin_control; - - if (haveChangeAddress) { - auto rvnChangeDest = DecodeDestination(rvnChangeAddress); - coin_control.destChange = rvnChangeDest; - } - LOCK2(cs_main, pwallet->cs_wallet); // Create and send the transaction @@ -1940,9 +2574,32 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: std::vector vecSend; int nChangePosRet = -1; bool fSubtractFeeFromAmount = false; + CRecipient recipient = {scriptPubKey, burnAmount, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - if (!pwallet->CreateTransactionWithAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, asset, DecodeDestination(address))) { + + // 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)); + + 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 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 + for (auto asset : assets) { + if (!VerifyWalletHasAsset(parentName + OWNER_TAG, error)) { + return false; + } + } + } + + 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); @@ -1951,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)) { @@ -1969,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 @@ -2019,29 +2667,8 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu return false; } - - // 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); - return false; - } - - // Get the outpoint that belongs to the Owner Asset - std::set myAssetOutPoints; - if (!passets->GetAssetsOutPoints(asset_name + OWNER_TAG, myAssetOutPoints)) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet can't find the owner token information for: ") + asset_name); - return false; - } - - // Check to make sure we have the right amount of outpoints - if (myAssetOutPoints.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 (myAssetOutPoints.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"); + // Verify that this wallet is the owner for the asset, and get the owner asset outpoint + if (!VerifyWalletHasAsset(asset_name + OWNER_TAG, error)) { return false; } @@ -2071,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; @@ -2083,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); @@ -2092,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; @@ -2113,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; @@ -2130,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)); @@ -2163,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; @@ -2184,6 +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 VerifyWalletHasAsset(const std::string& asset_name, std::pair& pairError) +{ + 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; + } + + std::vector vCoins; + std::map > mapAssetCoins; + pwallet->AvailableAssets(mapAssetCoins); + + if (mapAssetCoins.count(asset_name)) + return true; + + 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 abd0e98e32..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,20 +49,16 @@ class CWallet; class CReserveKey; class CWalletTx; struct CAssetOutputEntry; +class CCoinControl; +struct CBlockAssetUndo; // 50000 * 82 Bytes == 4.1 Mb #define MAX_CACHE_ASSETS_SIZE 50000 -enum AssetType -{ - ROOT, - OWNER, - SUB, - UNIQUE, - CHANNEL, - VOTE, - INVALID -}; +// 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: @@ -131,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; @@ -239,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); @@ -258,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; @@ -309,25 +310,37 @@ CAmount GetIssueAssetBurnAmount(); 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& 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); +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued); +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type); bool CheckReissueBurnTx(const CTxOut& txOut); bool CheckIssueDataTx(const CTxOut& txOut); @@ -335,22 +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); @@ -360,11 +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); +/** 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 a3d0a39d7b..2705f22e1b 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -1,20 +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; -class CNewAsset { +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); + +const char IPFS_SHA2_256 = 0x12; +const char IPFS_SHA2_256_LEN = 0x20; + +template +void ReadWriteIPFSHash(Stream& s, Operation ser_action, std::string& strIPFSHash) +{ + // 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 +{ public: std::string strName; // MAX 31 Bytes CAmount nAmount; // 8 Bytes @@ -29,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); @@ -45,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(); @@ -55,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); + } } }; @@ -74,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; @@ -94,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); } @@ -104,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; @@ -120,6 +205,7 @@ class CReissueAsset { { nAmount = 0; strName = ""; + nUnits = 0; nReissuable = 1; strIPFSHash = ""; } @@ -127,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; @@ -146,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; } }; @@ -163,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; } @@ -192,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; } }; @@ -217,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; } }; @@ -274,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() { @@ -290,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); @@ -307,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); } @@ -316,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/chain.h b/src/chain.h index 45bb663b53..a92a462578 100644 --- a/src/chain.h +++ b/src/chain.h @@ -20,6 +20,7 @@ * current network-adjusted time before the block will be accepted. */ static const int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; +static const int64_t MAX_FUTURE_BLOCK_TIME_DGW = MAX_FUTURE_BLOCK_TIME / 10; /** * Timestamp window used as a grace period by code that compares external diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3f0e6e03b3..bf06e1a47d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -128,16 +128,15 @@ 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].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); @@ -218,12 +218,19 @@ class CMainParams : public CChainParams { //Global Burn Address strGlobalBurnAddress = "RXBurnXXXXXXXXXXXXXXXXXXXXXXWUo9FV"; + + // 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 (v3) + * Testnet (v6) */ class CTestNetParams : public CChainParams { public: @@ -247,8 +254,8 @@ class CTestNetParams : public CChainParams { 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; - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 1532476800; // July 25, 2018 UTC - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 1564012800; // July 25, 2019 UTC + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 1533924000; // GMT: Friday, August 10, 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. @@ -262,21 +269,84 @@ class CTestNetParams : public CChainParams { pchMessageStart[1] = 0x56; pchMessageStart[2] = 0x4E; pchMessageStart[3] = 0x54; - nDefaultPort = 18767; + nDefaultPort = 18770; nPruneAfterHeight = 1000; - genesis = CreateGenesisBlock(1517350340, 4791361, 0x1e00ffff, 4, 5000 * COIN); + 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 +// ///////////////////////////////////////////////////////////////// + + +// arith_uint256 test; +// bool fNegative; +// bool fOverflow; +// test.SetCompact(0x1e00ffff, &fNegative, &fOverflow); +// std::cout << "Test threshold: " << test.GetHex() << "\n\n"; +// +// int genesisNonce = 0; +// uint256 TempHashHolding = uint256S("0x0000000000000000000000000000000000000000000000000000000000000000"); +// uint256 BestBlockHash = uint256S("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); +// for (int i=0;i<40000000;i++) { +// genesis = CreateGenesisBlock(nGenesisTime, i, 0x1e00ffff, 2, 5000 * COIN); +// //genesis.hashPrevBlock = TempHashHolding; +// consensus.hashGenesisBlock = genesis.GetHash(); +// +// arith_uint256 BestBlockHashArith = UintToArith256(BestBlockHash); +// if (UintToArith256(consensus.hashGenesisBlock) < BestBlockHashArith) { +// BestBlockHash = consensus.hashGenesisBlock; +// std::cout << BestBlockHash.GetHex() << " Nonce: " << i << "\n"; +// std::cout << " PrevBlockHash: " << genesis.hashPrevBlock.GetHex() << "\n"; +// } +// +// TempHashHolding = consensus.hashGenesisBlock; +// +// if (BestBlockHashArith < test) { +// genesisNonce = i - 1; +// break; +// } +// //std::cout << consensus.hashGenesisBlock.GetHex() << "\n"; +// } +// std::cout << "\n"; +// std::cout << "\n"; +// std::cout << "\n"; +// +// std::cout << "hashGenesisBlock to 0x" << BestBlockHash.GetHex() << std::endl; +// std::cout << "Genesis Nonce to " << genesisNonce << std::endl; +// std::cout << "Genesis Merkle " << genesis.hashMerkleRoot.GetHex() << std::endl; +// +// std::cout << "\n"; +// std::cout << "\n"; +// int totalHits = 0; +// double totalTime = 0.0; +// +// for(int x = 0; x < 16; x++) { +// totalHits += algoHashHits[x]; +// totalTime += algoHashTotal[x]; +// std::cout << "hash algo " << x << " hits " << algoHashHits[x] << " total " << algoHashTotal[x] << " avg " << algoHashTotal[x]/algoHashHits[x] << std::endl; +// } +// +// std::cout << "Totals: hash algo " << " hits " << totalHits << " total " << totalTime << " avg " << totalTime/totalHits << std::endl; +// +// genesis.hashPrevBlock = TempHashHolding; +// +// return; + +// ///////////////////////////////////////////////////////////////// + + genesis = CreateGenesisBlock(nGenesisTime, 15615880, 0x1e00ffff, 2, 5000 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); //Test MerkleRoot and GenesisBlock - assert(consensus.hashGenesisBlock == uint256S("0x000000055c6b201ac99ed634953f92bd52239f5b26e090ce3caab6ec81bec921")); - 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); @@ -319,7 +389,15 @@ class CTestNetParams : public CChainParams { // Global Burn Address strGlobalBurnAddress = "n1BurnXXXXXXXXXXXXXXXXXXXXXXU1qejP"; + + // 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 **/ + } }; @@ -346,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 = 4; + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 6; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 999999999999ULL; @@ -377,7 +455,7 @@ class CRegTestParams : public CChainParams { // uint256 TempHashHolding = uint256S("0x0000000000000000000000000000000000000000000000000000000000000000"); // uint256 BestBlockHash = uint256S("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // for (int i=0;i<40000000;i++) { -// genesis = CreateGenesisBlock(1524179366, i, 0x207fffff, 4, 5000 * COIN); +// genesis = CreateGenesisBlock(1533751200, i, 0x207fffff, 2, 5000 * COIN); // //genesis.hashPrevBlock = TempHashHolding; // consensus.hashGenesisBlock = genesis.GetHash(); // @@ -420,7 +498,7 @@ class CRegTestParams : public CChainParams { // genesis.hashPrevBlock = TempHashHolding; // // return; -// + // ///////////////////////////////////////////////////////////////// @@ -470,6 +548,13 @@ class CRegTestParams : public CChainParams { // Global Burn Address strGlobalBurnAddress = "n1BurnXXXXXXXXXXXXXXXXXXXXXXU1qejP"; + + // 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 91aed041e9..01c903eb84 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -100,6 +100,12 @@ class CChainParams const std::string& IssueSubAssetBurnAddress() const { return strIssueSubAssetBurnAddress; } const std::string& IssueUniqueAssetBurnAddress() const { return strIssueUniqueAssetBurnAddress; } 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: @@ -136,6 +142,12 @@ class CChainParams // Global Burn Address 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 10fb341f7a..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 = "testnet3"; + strDataDir = "testnet6"; } }; diff --git a/src/coins.cpp b/src/coins.cpp index bc2b1d92be..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); + } + } } } } @@ -196,7 +219,8 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout, CAssetsCache* assetsCache) { CCoinsMap::iterator it = FetchCoin(outpoint); - if (it == cacheCoins.end()) return false; + if (it == cacheCoins.end()) + return false; cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage(); /** RVN START */ @@ -216,8 +240,9 @@ bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout, CAsset /** RVN START */ if (AreAssetsDeployed()) { if (assetsCache) { - if (!assetsCache->TrySpendCoin(outpoint, tempCoin.out)) + if (!assetsCache->TrySpendCoin(outpoint, tempCoin.out)) { return error("%s : Failed to try and spend the asset. COutPoint : %s", __func__, outpoint.ToString()); + } } } /** RVN END */ diff --git a/src/coins.h b/src/coins.h index 68f8642111..168c97d9f3 100644 --- a/src/coins.h +++ b/src/coins.h @@ -20,6 +20,7 @@ #include #include +#include /** * A UTXO entry. @@ -79,7 +80,7 @@ class Coin } bool IsAsset() const { - return out.scriptPubKey.IsTransferAsset() || out.scriptPubKey.IsNewAsset() || out.scriptPubKey.IsOwnerAsset() || out.scriptPubKey.IsReissueAsset(); + return out.scriptPubKey.IsAssetScript(); } size_t DynamicMemoryUsage() const { @@ -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 e93f5db7d2..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,46 +186,53 @@ 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.IsAsset() && !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"); - // If we aren't reindexing - if (!fReindex) { - // 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"); - } else { - // For all other types of assets, make sure they are sending the right type of units - CNewAsset asset; - if (!assetCache->GetAssetIfExists(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"); - } + // 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"); } + } } } - /** RVN END */ } + /** RVN END */ - // Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock if (fCheckDuplicateInputs) { std::set vInOutPoints; for (const auto& txin : tx.vin) @@ -250,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)) @@ -262,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)) @@ -285,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"); + } + } + } } } } @@ -338,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)) { @@ -358,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 @@ -384,19 +454,64 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c totalOutputs.at(transfer.strName) += transfer.nAmount; else totalOutputs.insert(make_pair(transfer.strName, transfer.nAmount)); + + 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->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 (!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; + std::string address; + if (!ReissueAssetFromScript(txout.scriptPubKey, reissue, address)) + return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-reissue-bad-deserialize"); + + 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/core_io.h b/src/core_io.h index 0840e8e926..03645fa2c8 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -31,7 +31,6 @@ std::vector ParseHexUV(const UniValue& v, const std::string& strN std::string ValueFromAmountString(const CAmount& amount, const int8_t units); UniValue ValueFromAmount(const CAmount& amount, const int8_t units); UniValue ValueFromAmount(const CAmount& amount); -std::string StringFromAmount(const CAmount& amount, const int8_t units); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0); void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); diff --git a/src/core_write.cpp b/src/core_write.cpp index ca514da7c5..97b917a328 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -13,6 +13,7 @@ #include "serialize.h" #include "streams.h" #include +#include #include "util.h" #include "utilmoneystr.h" #include "utilstrencodings.h" @@ -23,6 +24,7 @@ std::string ValueFromAmountString(const CAmount& amount, const int8_t units) int64_t n_abs = (sign ? -amount : amount); int64_t quotient = n_abs / COIN; int64_t remainder = n_abs % COIN; + remainder = remainder / pow(10, 8 - units); if (units == 0 && remainder == 0) { return strprintf("%s%d", sign ? "-" : "", quotient); @@ -42,21 +44,6 @@ UniValue ValueFromAmount(const CAmount& amount) return ValueFromAmount(amount, 8); } -std::string StringFromAmount(const CAmount& amount, const int8_t units) -{ - bool sign = amount < 0; - int64_t n_abs = (sign ? -amount : amount); - int64_t quotient = n_abs / COIN; - int64_t remainder = n_abs % COIN; - - if (units == 0 && remainder == 0) { - return strprintf("%s%d", sign ? "-" : "", quotient); - } - else { - return strprintf("%s%d.%0" + std::to_string(units) + "d", sign ? "-" : "", quotient, remainder); - } -} - std::string FormatScript(const CScript& script) { std::string ret; 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/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/pow.cpp b/src/pow.cpp index fa3680d018..423038ad7f 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -12,6 +12,8 @@ #include "uint256.h" #include "util.h" #include "validation.h" +#include "chainparams.h" +#include "tinyformat.h" unsigned int static DarkGravityWave(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) { /* current difficulty formula, dash - DarkGravity v3, written by Evan Duffield - evan@dash.org */ @@ -19,7 +21,7 @@ unsigned int static DarkGravityWave(const CBlockIndex* pindexLast, const CBlockH unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact(); const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); - int64_t nPastBlocks = 60; + int64_t nPastBlocks = 180; // ~3hr // make sure we have at least (nPastBlocks + 1) blocks, otherwise just return powLimit if (!pindexLast || pindexLast->nHeight < nPastBlocks) { @@ -123,16 +125,18 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead int dgw = DarkGravityWave(pindexLast, pblock, params); int btc = GetNextWorkRequiredBTC(pindexLast, pblock, params); int64_t nPrevBlockTime = (pindexLast->pprev ? pindexLast->pprev->GetBlockTime() : pindexLast->GetBlockTime()); - if (AreAssetsDeployed()) { - LogPrintf("Block %s: found next work required using DGW: [%s] (BTC would have been [%s]\t(%+d)\t(%0.3f%%)\t(%s sec))\n", - pindexLast->nHeight, dgw, btc, btc - dgw, (float)(btc - dgw) * 100.0 / (float)dgw, pindexLast->GetBlockTime() - nPrevBlockTime); + + if (IsDGWActive(pindexLast->nHeight + 1)) { + LogPrint(BCLog::NET, "Block %s - version: %s: found next work required using DGW: [%s] (BTC would have been [%s]\t(%+d)\t(%0.3f%%)\t(%s sec))\n", + pindexLast->nHeight + 1, pblock->nVersion, dgw, btc, btc - dgw, (float)(btc - dgw) * 100.0 / (float)dgw, pindexLast->GetBlockTime() - nPrevBlockTime); return dgw; } else { - LogPrintf("Block %s: found next work required using BTC: [%s] (DGW would have been [%s]\t(%+d)\t(%0.3f%%)\t(%s sec))\n", - pindexLast->nHeight, btc, dgw, dgw - btc, (float)(dgw - btc) * 100.0 / (float)btc, pindexLast->GetBlockTime() - nPrevBlockTime); + LogPrint(BCLog::NET, "Block %s - version: %s: found next work required using BTC: [%s] (DGW would have been [%s]\t(%+d)\t(%0.3f%%)\t(%s sec))\n", + pindexLast->nHeight + 1, pblock->nVersion, btc, dgw, dgw - btc, (float)(dgw - btc) * 100.0 / (float)btc, pindexLast->GetBlockTime() - nPrevBlockTime); return btc; } + } unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params) 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 5178c9f040..5b90fe6701 100644 --- a/src/qt/assetrecord.h +++ b/src/qt/assetrecord.h @@ -18,22 +18,27 @@ 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) { } std::string formattedQuantity() { - int64_t quotient = quantity / COIN; - if (units == 0) { - return strprintf("%d", quotient); - } else { - int64_t remainder = quantity % COIN; - return strprintf("%d.%0" + std::to_string(units) + "d", quotient, remainder); + bool sign = quantity < 0; + int64_t n_abs = (sign ? -quantity : quantity); + int64_t quotient = n_abs / COIN; + int64_t remainder = n_abs % COIN; + remainder = remainder / pow(10, 8 - units); + + if (units == 0 && remainder == 0) { + return strprintf("%s%d", sign ? "-" : "", quotient); + } + else { + return strprintf("%s%d.%0" + std::to_string(units) + "d", sign ? "-" : "", quotient, remainder); } } @@ -42,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 dabbdf1523..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(); @@ -306,7 +294,7 @@ void AssetsDialog::on_sendButton_clicked() for (SendAssetsRecipient &rcp : recipients) { // generate bold amount string - QString amount = "" + QString::fromStdString(StringFromAmount(rcp.amount, 8)) + " " + rcp.assetName; + QString amount = "" + QString::fromStdString(ValueFromAmountString(rcp.amount, 8)) + " " + rcp.assetName; amount.append(""); // generate monospace address string QString address = "" + rcp.address; @@ -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 113443feb2..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