mirror of
https://github.com/monero-project/monero.git
synced 2024-11-17 16:27:39 +00:00
Merge pull request #5331
32973434
python-rpc: add getblockheadersrange daemon RPC (moneromooo-monero)c7bfdc35
python-rpc: add console.py (moneromooo-monero)22b644f4
functional_tests: move RPC API to utils, it is not test specific (moneromooo-monero)30c865f0
functional_tests: add balance tests (moneromooo-monero)fdfa832f
functional_tests: add missing parameters to get_balance (moneromooo-monero)cf6d7759
functional_tests: add proofs tests (tx key, in/out tx, reserve) (moneromooo-monero)a3144bd7
functional_tests: add more transfer tests (moneromooo-monero)5d580bfa
functional_tests: add get_bulk_transfer tests (moneromooo-monero)0becbd16
functional_tests: add message signing/verification tests (moneromooo-monero)a5dbf7f5
functional_tests: add multisig and cold signing tests (moneromooo-monero)b2fc5719
functional_tests: support several daemons/wallets (moneromooo-monero)9e979ffa
functional_tests: add txpool RPC tests (moneromooo-monero)3e93c157
functional_tests: add integrated address tests (moneromooo-monero)b384309e
functional_tests: add basic transfer tests (moneromooo-monero)ef7681b6
functional_tests: plug RPC tests into the cmake machinery (moneromooo-monero)18a2ed45
functional_tests: add basic mining tests (moneromooo-monero)98e280fc
functional_tests: add wallet address/subaddress RPC tests (moneromooo-monero)8dcd4d3d
functional_tests: improve RPC blockchain tests (moneromooo-monero)
This commit is contained in:
commit
e601028649
21 changed files with 3002 additions and 287 deletions
|
@ -49,6 +49,7 @@ target_link_libraries(functional_tests
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
${EXTRA_LIBRARIES})
|
${EXTRA_LIBRARIES})
|
||||||
set_property(TARGET functional_tests
|
|
||||||
PROPERTY
|
add_test(
|
||||||
FOLDER "tests")
|
NAME functional_tests_rpc
|
||||||
|
COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/functional_tests_rpc.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" all)
|
||||||
|
|
|
@ -28,75 +28,129 @@
|
||||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
"""Test blockchain RPC calls
|
import time
|
||||||
|
|
||||||
|
"""Test daemon blockchain RPC calls
|
||||||
|
|
||||||
Test the following RPCs:
|
Test the following RPCs:
|
||||||
- get_info
|
- get_info
|
||||||
- generateblocks
|
- generateblocks
|
||||||
|
- misc block retrieval
|
||||||
|
- pop_blocks
|
||||||
- [TODO: many tests still need to be written]
|
- [TODO: many tests still need to be written]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from test_framework.daemon import Daemon
|
from framework.daemon import Daemon
|
||||||
from test_framework.wallet import Wallet
|
|
||||||
|
|
||||||
class BlockchainTest():
|
class BlockchainTest():
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self._test_get_info()
|
|
||||||
self._test_hardfork_info()
|
|
||||||
self._test_generateblocks(5)
|
self._test_generateblocks(5)
|
||||||
|
|
||||||
def _test_get_info(self):
|
|
||||||
print('Test get_info')
|
|
||||||
|
|
||||||
daemon = Daemon()
|
|
||||||
res = daemon.get_info()
|
|
||||||
|
|
||||||
# difficulty should be set to 1 for this test
|
|
||||||
assert 'difficulty' in res.keys()
|
|
||||||
assert res['difficulty'] == 1;
|
|
||||||
|
|
||||||
# nettype should not be TESTNET
|
|
||||||
assert 'testnet' in res.keys()
|
|
||||||
assert res['testnet'] == False;
|
|
||||||
|
|
||||||
# nettype should not be STAGENET
|
|
||||||
assert 'stagenet' in res.keys()
|
|
||||||
assert res['stagenet'] == False;
|
|
||||||
|
|
||||||
# nettype should be FAKECHAIN
|
|
||||||
assert 'nettype' in res.keys()
|
|
||||||
assert res['nettype'] == "fakechain";
|
|
||||||
|
|
||||||
# free_space should be > 0
|
|
||||||
assert 'free_space' in res.keys()
|
|
||||||
assert res['free_space'] > 0
|
|
||||||
|
|
||||||
# height should be greater or equal to 1
|
|
||||||
assert 'height' in res.keys()
|
|
||||||
assert res['height'] >= 1
|
|
||||||
|
|
||||||
|
|
||||||
def _test_hardfork_info(self):
|
|
||||||
print('Test hard_fork_info')
|
|
||||||
|
|
||||||
daemon = Daemon()
|
|
||||||
res = daemon.hard_fork_info()
|
|
||||||
|
|
||||||
# hard_fork version should be set at height 1
|
|
||||||
assert 'earliest_height' in res.keys()
|
|
||||||
assert res['earliest_height'] == 1;
|
|
||||||
|
|
||||||
|
|
||||||
def _test_generateblocks(self, blocks):
|
def _test_generateblocks(self, blocks):
|
||||||
print("Test generating", blocks, 'blocks')
|
assert blocks >= 2
|
||||||
|
|
||||||
|
print "Test generating", blocks, 'blocks'
|
||||||
|
|
||||||
daemon = Daemon()
|
daemon = Daemon()
|
||||||
res = daemon.get_info()
|
|
||||||
height = res['height']
|
|
||||||
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
|
|
||||||
|
|
||||||
assert res['height'] == height + blocks - 1
|
# check info/height before generating blocks
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
height = res_info.height
|
||||||
|
prev_block = res_info.top_block_hash
|
||||||
|
res_height = daemon.get_height()
|
||||||
|
assert res_height.height == height
|
||||||
|
assert int(res_info.wide_cumulative_difficulty) == (res_info.cumulative_difficulty_top64 << 64) + res_info.cumulative_difficulty
|
||||||
|
cumulative_difficulty = int(res_info.wide_cumulative_difficulty)
|
||||||
|
|
||||||
|
# we should not see a block at height
|
||||||
|
ok = False
|
||||||
|
try: daemon.getblock(height)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
# generate blocks
|
||||||
|
res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
|
||||||
|
|
||||||
|
# check info/height after generateblocks blocks
|
||||||
|
assert res_generateblocks.height == height + blocks - 1
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
assert res_info.height == height + blocks
|
||||||
|
assert res_info.top_block_hash != prev_block
|
||||||
|
res_height = daemon.get_height()
|
||||||
|
assert res_height.height == height + blocks
|
||||||
|
|
||||||
|
# get the blocks, check they have the right height
|
||||||
|
res_getblock = []
|
||||||
|
for n in range(blocks):
|
||||||
|
res_getblock.append(daemon.getblock(height + n))
|
||||||
|
block_header = res_getblock[n].block_header
|
||||||
|
assert abs(block_header.timestamp - time.time()) < 10 # within 10 seconds
|
||||||
|
assert block_header.height == height + n
|
||||||
|
assert block_header.orphan_status == False
|
||||||
|
assert block_header.depth == blocks - n - 1
|
||||||
|
assert block_header.prev_hash == prev_block, prev_block
|
||||||
|
assert int(block_header.wide_difficulty) == (block_header.difficulty_top64 << 64) + block_header.difficulty
|
||||||
|
assert int(block_header.wide_cumulative_difficulty) == (block_header.cumulative_difficulty_top64 << 64) + block_header.cumulative_difficulty
|
||||||
|
assert block_header.reward >= 600000000000 # tail emission
|
||||||
|
cumulative_difficulty += int(block_header.wide_difficulty)
|
||||||
|
assert cumulative_difficulty == int(block_header.wide_cumulative_difficulty)
|
||||||
|
assert block_header.block_size > 0
|
||||||
|
assert block_header.block_weight >= block_header.block_size
|
||||||
|
assert block_header.long_term_weight > 0
|
||||||
|
prev_block = block_header.hash
|
||||||
|
|
||||||
|
# we should not see a block after that
|
||||||
|
ok = False
|
||||||
|
try: daemon.getblock(height + blocks)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
# getlastblockheader and by height/hash should return the same block
|
||||||
|
res_getlastblockheader = daemon.getlastblockheader()
|
||||||
|
assert res_getlastblockheader.block_header == block_header
|
||||||
|
res_getblockheaderbyhash = daemon.getblockheaderbyhash(prev_block)
|
||||||
|
assert res_getblockheaderbyhash.block_header == block_header
|
||||||
|
res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 1)
|
||||||
|
assert res_getblockheaderbyheight.block_header == block_header
|
||||||
|
|
||||||
|
# getting a block template after that should have the right height, etc
|
||||||
|
res_getblocktemplate = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
|
||||||
|
assert res_getblocktemplate.height == height + blocks
|
||||||
|
assert res_getblocktemplate.reserved_offset > 0
|
||||||
|
assert res_getblocktemplate.prev_hash == res_info.top_block_hash
|
||||||
|
assert res_getblocktemplate.expected_reward >= 600000000000
|
||||||
|
assert len(res_getblocktemplate.blocktemplate_blob) > 0
|
||||||
|
assert len(res_getblocktemplate.blockhashing_blob) > 0
|
||||||
|
assert int(res_getblocktemplate.wide_difficulty) == (res_getblocktemplate.difficulty_top64 << 64) + res_getblocktemplate.difficulty
|
||||||
|
|
||||||
|
# diff etc should be the same
|
||||||
|
assert res_getblocktemplate.prev_hash == res_info.top_block_hash
|
||||||
|
|
||||||
|
res_getlastblockheader = daemon.getlastblockheader()
|
||||||
|
|
||||||
|
# pop a block
|
||||||
|
res_popblocks = daemon.pop_blocks(1)
|
||||||
|
assert res_popblocks.height == height + blocks - 1
|
||||||
|
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
assert res_info.height == height + blocks - 1
|
||||||
|
|
||||||
|
# getlastblockheader and by height/hash should return the previous block
|
||||||
|
block_header = res_getblock[blocks - 2].block_header
|
||||||
|
block_header.depth = 0 # this will be different, ignore it
|
||||||
|
res_getlastblockheader = daemon.getlastblockheader()
|
||||||
|
assert res_getlastblockheader.block_header == block_header
|
||||||
|
res_getblockheaderbyhash = daemon.getblockheaderbyhash(block_header.hash)
|
||||||
|
assert res_getblockheaderbyhash.block_header == block_header
|
||||||
|
res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 2)
|
||||||
|
assert res_getblockheaderbyheight.block_header == block_header
|
||||||
|
|
||||||
|
# we should not see the popped block anymore
|
||||||
|
ok = False
|
||||||
|
try: daemon.getblock(height + blocks - 1)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
146
tests/functional_tests/cold_signing.py
Executable file
146
tests/functional_tests/cold_signing.py
Executable file
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test cold tx signing
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class ColdSigningTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create(0)
|
||||||
|
self.mine()
|
||||||
|
self.transfer()
|
||||||
|
|
||||||
|
def create(self, idx):
|
||||||
|
print 'Creating hot and cold wallet'
|
||||||
|
|
||||||
|
self.hot_wallet = Wallet(idx = 0)
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: self.hot_wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
self.cold_wallet = Wallet(idx = 1)
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: self.cold_wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
||||||
|
res = self.cold_wallet.restore_deterministic_wallet(seed = seed)
|
||||||
|
self.cold_wallet.set_daemon('127.0.0.1:11111', ssl_support = "disabled")
|
||||||
|
spend_key = self.cold_wallet.query_key("spend_key").key
|
||||||
|
view_key = self.cold_wallet.query_key("view_key").key
|
||||||
|
res = self.hot_wallet.generate_from_keys(viewkey = view_key, address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.hot_wallet.query_key("spend_key")
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
ok = False
|
||||||
|
try: self.hot_wallet.query_key("mnemonic")
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
assert self.cold_wallet.query_key("view_key").key == view_key
|
||||||
|
assert self.cold_wallet.get_address().address == self.hot_wallet.get_address().address
|
||||||
|
|
||||||
|
def mine(self):
|
||||||
|
print("Mining some blocks")
|
||||||
|
daemon = Daemon()
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
|
||||||
|
wallet.refresh()
|
||||||
|
|
||||||
|
def transfer(self):
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print("Creating transaction in hot wallet")
|
||||||
|
|
||||||
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
||||||
|
payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'
|
||||||
|
|
||||||
|
res = self.hot_wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False)
|
||||||
|
assert len(res.tx_hash) == 32*2
|
||||||
|
txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 0
|
||||||
|
assert res.amount > 0
|
||||||
|
amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) == 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) == 0
|
||||||
|
assert len(res.unsigned_txset) > 0
|
||||||
|
unsigned_txset = res.unsigned_txset
|
||||||
|
|
||||||
|
print 'Signing transaction with cold wallet'
|
||||||
|
res = self.cold_wallet.sign_transfer(unsigned_txset)
|
||||||
|
assert len(res.signed_txset) > 0
|
||||||
|
signed_txset = res.signed_txset
|
||||||
|
assert len(res.tx_hash_list) == 1
|
||||||
|
txid = res.tx_hash_list[0]
|
||||||
|
assert len(txid) == 64
|
||||||
|
|
||||||
|
print 'Submitting transaction with hot wallet'
|
||||||
|
res = self.hot_wallet.submit_transfer(signed_txset)
|
||||||
|
assert len(res.tx_hash_list) > 0
|
||||||
|
assert res.tx_hash_list[0] == txid
|
||||||
|
|
||||||
|
res = self.hot_wallet.get_transfers()
|
||||||
|
assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 1
|
||||||
|
assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
self.hot_wallet.refresh()
|
||||||
|
|
||||||
|
res = self.hot_wallet.get_transfers()
|
||||||
|
assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0
|
||||||
|
assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1
|
||||||
|
|
||||||
|
res = self.hot_wallet.get_tx_key(txid)
|
||||||
|
assert len(res.tx_key) == 0 or res.tx_key == '01' + '0' * 62 # identity is used as placeholder
|
||||||
|
res = self.cold_wallet.get_tx_key(txid)
|
||||||
|
assert len(res.tx_key) == 64
|
||||||
|
|
||||||
|
|
||||||
|
class Guard:
|
||||||
|
def __enter__(self):
|
||||||
|
for i in range(2):
|
||||||
|
Wallet(idx = i).auto_refresh(False)
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
for i in range(2):
|
||||||
|
Wallet(idx = i).auto_refresh(True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with Guard() as guard:
|
||||||
|
cs = ColdSigningTest().run_test()
|
89
tests/functional_tests/daemon_info.py
Executable file
89
tests/functional_tests/daemon_info.py
Executable file
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2018 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""Test daemon RPC calls
|
||||||
|
|
||||||
|
Test the following RPCs:
|
||||||
|
- get_info
|
||||||
|
- hard_fork_info
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
|
||||||
|
class DaemonGetInfoTest():
|
||||||
|
def run_test(self):
|
||||||
|
self._test_hardfork_info()
|
||||||
|
self._test_get_info()
|
||||||
|
|
||||||
|
def _test_hardfork_info(self):
|
||||||
|
print('Test hard_fork_info')
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
res = daemon.hard_fork_info()
|
||||||
|
|
||||||
|
# hard_fork version should be set at height 1
|
||||||
|
assert 'earliest_height' in res.keys()
|
||||||
|
#assert res['earliest_height'] == 1;
|
||||||
|
assert res.earliest_height == 1
|
||||||
|
|
||||||
|
def _test_get_info(self):
|
||||||
|
print('Test get_info')
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
res = daemon.get_info()
|
||||||
|
|
||||||
|
# difficulty should be set to 1 for this test
|
||||||
|
assert 'difficulty' in res.keys()
|
||||||
|
assert res.difficulty == 1;
|
||||||
|
|
||||||
|
# nettype should not be TESTNET
|
||||||
|
assert 'testnet' in res.keys()
|
||||||
|
assert res.testnet == False;
|
||||||
|
|
||||||
|
# nettype should not be STAGENET
|
||||||
|
assert 'stagenet' in res.keys()
|
||||||
|
assert res.stagenet == False;
|
||||||
|
|
||||||
|
# nettype should be FAKECHAIN
|
||||||
|
assert 'nettype' in res.keys()
|
||||||
|
assert res.nettype == "fakechain";
|
||||||
|
|
||||||
|
# free_space should be > 0
|
||||||
|
assert 'free_space' in res.keys()
|
||||||
|
assert res.free_space > 0
|
||||||
|
|
||||||
|
# height should be greater or equal to 1
|
||||||
|
assert 'height' in res.keys()
|
||||||
|
assert res.height >= 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
DaemonGetInfoTest().run_test()
|
135
tests/functional_tests/functional_tests_rpc.py
Executable file
135
tests/functional_tests/functional_tests_rpc.py
Executable file
|
@ -0,0 +1,135 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from signal import SIGTERM
|
||||||
|
import socket
|
||||||
|
import string
|
||||||
|
import os
|
||||||
|
|
||||||
|
USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]'
|
||||||
|
DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message', 'proofs']
|
||||||
|
try:
|
||||||
|
python = sys.argv[1]
|
||||||
|
srcdir = sys.argv[2]
|
||||||
|
builddir = sys.argv[3]
|
||||||
|
except:
|
||||||
|
print(USAGE)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.argv[4]
|
||||||
|
except:
|
||||||
|
print(USAGE)
|
||||||
|
print('Available tests: ' + string.join(DEFAULT_TESTS, ', '))
|
||||||
|
print('Or run all with "all"')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tests = sys.argv[4:]
|
||||||
|
if tests == ['all']:
|
||||||
|
tests = DEFAULT_TESTS
|
||||||
|
except:
|
||||||
|
tests = DEFAULT_TESTS
|
||||||
|
|
||||||
|
N_MONERODS = 1
|
||||||
|
N_WALLETS = 4
|
||||||
|
|
||||||
|
monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
|
||||||
|
wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"]
|
||||||
|
|
||||||
|
command_lines = []
|
||||||
|
processes = []
|
||||||
|
outputs = []
|
||||||
|
ports = []
|
||||||
|
|
||||||
|
for i in range(N_MONERODS):
|
||||||
|
command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else x for x in monerod_base])
|
||||||
|
outputs.append(open(builddir + '/tests/functional_tests/monerod' + str(i) + '.log', 'a+'))
|
||||||
|
ports.append(18180+i)
|
||||||
|
|
||||||
|
for i in range(N_WALLETS):
|
||||||
|
command_lines.append([str(18090+i) if x == "wallet_port" else x for x in wallet_base])
|
||||||
|
outputs.append(open(builddir + '/tests/functional_tests/wallet' + str(i) + '.log', 'a+'))
|
||||||
|
ports.append(18090+i)
|
||||||
|
|
||||||
|
print('Starting servers...')
|
||||||
|
try:
|
||||||
|
PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else ''
|
||||||
|
if len(PYTHONPATH) > 0:
|
||||||
|
PYTHONPATH += ':'
|
||||||
|
PYTHONPATH += srcdir + '/../../utils/python-rpc'
|
||||||
|
os.environ['PYTHONPATH'] = PYTHONPATH
|
||||||
|
for i in range(len(command_lines)):
|
||||||
|
#print('Running: ' + str(command_lines[i]))
|
||||||
|
processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i]))
|
||||||
|
except Exception, e:
|
||||||
|
print('Error: ' + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def kill():
|
||||||
|
for i in range(len(processes)):
|
||||||
|
try: processes[i].send_signal(SIGTERM)
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
# wait for error/startup
|
||||||
|
for i in range(10):
|
||||||
|
time.sleep(1)
|
||||||
|
all_open = True
|
||||||
|
for port in ports:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(1)
|
||||||
|
if s.connect_ex(('127.0.0.1', port)) != 0:
|
||||||
|
all_open = False
|
||||||
|
break
|
||||||
|
s.close()
|
||||||
|
if all_open:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not all_open:
|
||||||
|
print('Failed to start wallet or daemon')
|
||||||
|
kill()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
PASS = []
|
||||||
|
FAIL = []
|
||||||
|
for test in tests:
|
||||||
|
try:
|
||||||
|
print('[TEST STARTED] ' + test)
|
||||||
|
cmd = [python, srcdir + '/' + test + ".py"]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
PASS.append(test)
|
||||||
|
print('[TEST PASSED] ' + test)
|
||||||
|
except:
|
||||||
|
FAIL.append(test)
|
||||||
|
print('[TEST FAILED] ' + test)
|
||||||
|
pass
|
||||||
|
|
||||||
|
print('Stopping servers...')
|
||||||
|
kill()
|
||||||
|
|
||||||
|
# wait for exit, the poll method does not work (https://bugs.python.org/issue2475) so we wait, possibly forever if the process hangs
|
||||||
|
if True:
|
||||||
|
for p in processes:
|
||||||
|
p.wait()
|
||||||
|
else:
|
||||||
|
for i in range(10):
|
||||||
|
n_returncode = 0
|
||||||
|
for p in processes:
|
||||||
|
p.poll()
|
||||||
|
if p.returncode:
|
||||||
|
n_returncode += 1
|
||||||
|
if n_returncode == len(processes):
|
||||||
|
print('All done: ' + string.join([x.returncode for x in processes], ', '))
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
for p in processes:
|
||||||
|
if not p.returncode:
|
||||||
|
print('Failed to stop process')
|
||||||
|
|
||||||
|
if len(FAIL) == 0:
|
||||||
|
print('Done, ' + str(len(PASS)) + '/' + str(len(tests)) + ' tests passed')
|
||||||
|
else:
|
||||||
|
print('Done, ' + str(len(FAIL)) + '/' + str(len(tests)) + ' tests failed: ' + string.join(FAIL, ', '))
|
101
tests/functional_tests/integrated_address.py
Executable file
101
tests/functional_tests/integrated_address.py
Executable file
|
@ -0,0 +1,101 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test integrated address RPC calls
|
||||||
|
|
||||||
|
Test the following RPCs:
|
||||||
|
- make_integrated_address
|
||||||
|
- split_integrated_address
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class IntegratedAddressTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallet'
|
||||||
|
wallet = Wallet()
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
||||||
|
res = wallet.restore_deterministic_wallet(seed = seed)
|
||||||
|
assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert res.seed == seed
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
print 'Checking local address'
|
||||||
|
res = wallet.make_integrated_address(payment_id = '0123456789abcdef')
|
||||||
|
assert res.integrated_address == '4CMe2PUhs4J4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfSbLRB61BQVATzerHGj'
|
||||||
|
assert res.payment_id == '0123456789abcdef'
|
||||||
|
res = wallet.split_integrated_address(res.integrated_address)
|
||||||
|
assert res.standard_address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert res.payment_id == '0123456789abcdef'
|
||||||
|
|
||||||
|
print 'Checking different address'
|
||||||
|
res = wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788')
|
||||||
|
assert res.integrated_address == '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo'
|
||||||
|
assert res.payment_id == '1122334455667788'
|
||||||
|
res = wallet.split_integrated_address(res.integrated_address)
|
||||||
|
assert res.standard_address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK'
|
||||||
|
assert res.payment_id == '1122334455667788'
|
||||||
|
|
||||||
|
print 'Checking bad payment id'
|
||||||
|
fails = 0
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '11223344556677880')
|
||||||
|
except: fails += 1
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778')
|
||||||
|
except: fails += 1
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '')
|
||||||
|
except: fails += 1
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778g')
|
||||||
|
except: fails += 1
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788112233445566778811223344556677881122334455667788')
|
||||||
|
except: fails += 1
|
||||||
|
assert fails == 5
|
||||||
|
|
||||||
|
print 'Checking bad standard address'
|
||||||
|
fails = 0
|
||||||
|
try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerr', payment_id = '1122334455667788')
|
||||||
|
except: fails += 1
|
||||||
|
try: wallet.make_integrated_address(standard_address = '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo', payment_id = '1122334455667788')
|
||||||
|
except: fails += 1
|
||||||
|
assert fails == 2
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
IntegratedAddressTest().run_test()
|
123
tests/functional_tests/mining.py
Executable file
123
tests/functional_tests/mining.py
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2018 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test daemon mining RPC calls
|
||||||
|
|
||||||
|
Test the following RPCs:
|
||||||
|
- start_mining
|
||||||
|
- stop_mining
|
||||||
|
- mining_status
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class MiningTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.mine()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallet'
|
||||||
|
wallet = Wallet()
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
res = wallet.restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted')
|
||||||
|
|
||||||
|
def mine(self):
|
||||||
|
print "Test mining"
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
# check info/height/balance before generating blocks
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
prev_height = res_info.height
|
||||||
|
res_getbalance = wallet.get_balance()
|
||||||
|
prev_balance = res_getbalance.balance
|
||||||
|
|
||||||
|
res_status = daemon.mining_status()
|
||||||
|
|
||||||
|
res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1)
|
||||||
|
|
||||||
|
res_status = daemon.mining_status()
|
||||||
|
assert res_status.active == True
|
||||||
|
assert res_status.threads_count == 1
|
||||||
|
assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert res_status.is_background_mining_enabled == False
|
||||||
|
assert res_status.block_reward >= 600000000000
|
||||||
|
|
||||||
|
# wait till we mined a few of them
|
||||||
|
timeout = 5
|
||||||
|
timeout_height = prev_height
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
height = res_info.height
|
||||||
|
if height >= prev_height + 5:
|
||||||
|
break
|
||||||
|
if height > timeout_height:
|
||||||
|
timeout = 5
|
||||||
|
timeout_height = height
|
||||||
|
else:
|
||||||
|
timeout -= 1
|
||||||
|
assert timeout >= 0
|
||||||
|
|
||||||
|
res = daemon.stop_mining()
|
||||||
|
res_status = daemon.mining_status()
|
||||||
|
assert res_status.active == False
|
||||||
|
|
||||||
|
res_info = daemon.get_info()
|
||||||
|
new_height = res_info.height
|
||||||
|
|
||||||
|
wallet.refresh()
|
||||||
|
res_getbalance = wallet.get_balance()
|
||||||
|
balance = res_getbalance.balance
|
||||||
|
assert balance >= prev_balance + (new_height - prev_height) * 600000000000
|
||||||
|
|
||||||
|
res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1, do_background_mining = True)
|
||||||
|
res_status = daemon.mining_status()
|
||||||
|
assert res_status.active == True
|
||||||
|
assert res_status.threads_count == 1
|
||||||
|
assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert res_status.is_background_mining_enabled == True
|
||||||
|
assert res_status.block_reward >= 600000000000
|
||||||
|
|
||||||
|
# don't wait, might be a while if the machine is busy, which it probably is
|
||||||
|
res = daemon.stop_mining()
|
||||||
|
res_status = daemon.mining_status()
|
||||||
|
assert res_status.active == False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
MiningTest().run_test()
|
227
tests/functional_tests/multisig.py
Executable file
227
tests/functional_tests/multisig.py
Executable file
|
@ -0,0 +1,227 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test multisig transfers
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class MultisigTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5)
|
||||||
|
self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5)
|
||||||
|
self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5)
|
||||||
|
self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5)
|
||||||
|
self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60)
|
||||||
|
|
||||||
|
self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk')
|
||||||
|
self.import_multisig_info([1, 0], 5)
|
||||||
|
txid = self.transfer([1, 0])
|
||||||
|
self.import_multisig_info([0, 1], 6)
|
||||||
|
self.check_transaction(txid)
|
||||||
|
|
||||||
|
self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y')
|
||||||
|
self.import_multisig_info([0, 2], 5)
|
||||||
|
txid = self.transfer([0, 2])
|
||||||
|
self.import_multisig_info([0, 1, 2], 6)
|
||||||
|
self.check_transaction(txid)
|
||||||
|
|
||||||
|
self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53')
|
||||||
|
self.import_multisig_info([0, 2, 3], 5)
|
||||||
|
txid = self.transfer([0, 2, 3])
|
||||||
|
self.import_multisig_info([0, 1, 2, 3], 6)
|
||||||
|
self.check_transaction(txid)
|
||||||
|
|
||||||
|
self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR')
|
||||||
|
self.import_multisig_info([1, 2], 5)
|
||||||
|
txid = self.transfer([1, 2])
|
||||||
|
self.import_multisig_info([0, 1, 2, 3], 6)
|
||||||
|
self.check_transaction(txid)
|
||||||
|
|
||||||
|
def mine(self, address, blocks):
|
||||||
|
print("Mining some blocks")
|
||||||
|
daemon = Daemon()
|
||||||
|
daemon.generateblocks(address, blocks)
|
||||||
|
|
||||||
|
def create_multisig_wallets(self, M_threshold, N_total, expected_address):
|
||||||
|
print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet')
|
||||||
|
seeds = [
|
||||||
|
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
||||||
|
'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
|
||||||
|
'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid',
|
||||||
|
'waking gown buffet negative reorder speedy baffles hotel pliers dewdrop actress diplomat lymph emit ajar mailed kennel cynical jaunt justice weavers height teardrop toyed lymph',
|
||||||
|
]
|
||||||
|
assert M_threshold <= N_total
|
||||||
|
assert N_total <= len(seeds)
|
||||||
|
self.wallet = [None] * N_total
|
||||||
|
info = []
|
||||||
|
for i in range(N_total):
|
||||||
|
self.wallet[i] = Wallet(idx = i)
|
||||||
|
try: self.wallet[i].close_wallet()
|
||||||
|
except: pass
|
||||||
|
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
||||||
|
res = self.wallet[i].prepare_multisig()
|
||||||
|
assert len(res.multisig_info) > 0
|
||||||
|
info.append(res.multisig_info)
|
||||||
|
|
||||||
|
for i in range(N_total):
|
||||||
|
res = self.wallet[i].is_multisig()
|
||||||
|
assert res.multisig == False
|
||||||
|
|
||||||
|
addresses = []
|
||||||
|
next_stage = []
|
||||||
|
for i in range(N_total):
|
||||||
|
res = self.wallet[i].make_multisig(info, M_threshold)
|
||||||
|
addresses.append(res.address)
|
||||||
|
next_stage.append(res.multisig_info)
|
||||||
|
|
||||||
|
for i in range(N_total):
|
||||||
|
res = self.wallet[i].is_multisig()
|
||||||
|
assert res.multisig == True
|
||||||
|
assert res.ready == (M_threshold == N_total)
|
||||||
|
assert res.threshold == M_threshold
|
||||||
|
assert res.total == N_total
|
||||||
|
|
||||||
|
while True:
|
||||||
|
n_empty = 0
|
||||||
|
for i in range(len(next_stage)):
|
||||||
|
if len(next_stage[i]) == 0:
|
||||||
|
n_empty += 1
|
||||||
|
assert n_empty == 0 or n_empty == len(next_stage)
|
||||||
|
if n_empty == len(next_stage):
|
||||||
|
break
|
||||||
|
info = next_stage
|
||||||
|
next_stage = []
|
||||||
|
addresses = []
|
||||||
|
for i in range(N_total):
|
||||||
|
res = self.wallet[i].exchange_multisig_keys(info)
|
||||||
|
next_stage.append(res.multisig_info)
|
||||||
|
addresses.append(res.address)
|
||||||
|
for i in range(N_total):
|
||||||
|
assert addresses[i] == expected_address
|
||||||
|
|
||||||
|
for i in range(N_total):
|
||||||
|
res = self.wallet[i].is_multisig()
|
||||||
|
assert res.multisig == True
|
||||||
|
assert res.ready == True
|
||||||
|
assert res.threshold == M_threshold
|
||||||
|
assert res.total == N_total
|
||||||
|
|
||||||
|
|
||||||
|
def import_multisig_info(self, signers, expected_outputs):
|
||||||
|
assert len(signers) >= 2
|
||||||
|
|
||||||
|
print('Importing multisig info from ' + str(signers))
|
||||||
|
|
||||||
|
info = []
|
||||||
|
for i in signers:
|
||||||
|
self.wallet[i].refresh()
|
||||||
|
res = self.wallet[i].export_multisig_info()
|
||||||
|
assert len(res.info) > 0
|
||||||
|
info.append(res.info)
|
||||||
|
for i in signers:
|
||||||
|
res = self.wallet[i].import_multisig_info(info)
|
||||||
|
assert res.n_outputs == expected_outputs
|
||||||
|
|
||||||
|
def transfer(self, signers):
|
||||||
|
assert len(signers) >= 2
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print("Creating multisig transaction from wallet " + str(signers[0]))
|
||||||
|
|
||||||
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
||||||
|
res = self.wallet[signers[0]].transfer([dst])
|
||||||
|
assert len(res.tx_hash) == 0 # not known yet
|
||||||
|
txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 32*2
|
||||||
|
assert res.amount > 0
|
||||||
|
amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) == 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) > 0
|
||||||
|
assert len(res.unsigned_txset) == 0
|
||||||
|
multisig_txset = res.multisig_txset
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
for i in range(len(self.wallet)):
|
||||||
|
self.wallet[i].refresh()
|
||||||
|
|
||||||
|
for i in range(len(signers[1:])):
|
||||||
|
print('Signing multisig transaction with wallet ' + str(signers[i+1]))
|
||||||
|
res = self.wallet[signers[i+1]].sign_multisig(multisig_txset)
|
||||||
|
multisig_txset = res.tx_data_hex
|
||||||
|
assert len(res.tx_hash_list if 'tx_hash_list' in res else []) == (i == len(signers[1:]) - 1)
|
||||||
|
|
||||||
|
if i < len(signers[1:]) - 1:
|
||||||
|
print('Submitting multisig transaction prematurely with wallet ' + str(signers[-1]))
|
||||||
|
ok = False
|
||||||
|
try: self.wallet[signers[-1]].submit_multisig(multisig_txset)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
print('Submitting multisig transaction with wallet ' + str(signers[-1]))
|
||||||
|
res = self.wallet[signers[-1]].submit_multisig(multisig_txset)
|
||||||
|
assert len(res.tx_hash_list) == 1
|
||||||
|
txid = res.tx_hash_list[0]
|
||||||
|
|
||||||
|
for i in range(len(self.wallet)):
|
||||||
|
self.wallet[i].refresh()
|
||||||
|
res = self.wallet[i].get_transfers()
|
||||||
|
assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == (1 if i == signers[-1] else 0)
|
||||||
|
assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
def check_transaction(self, txid):
|
||||||
|
for i in range(len(self.wallet)):
|
||||||
|
self.wallet[i].refresh()
|
||||||
|
res = self.wallet[i].get_transfers()
|
||||||
|
assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0
|
||||||
|
assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1
|
||||||
|
|
||||||
|
|
||||||
|
class Guard:
|
||||||
|
def __enter__(self):
|
||||||
|
for i in range(4):
|
||||||
|
Wallet(idx = i).auto_refresh(False)
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
for i in range(4):
|
||||||
|
Wallet(idx = i).auto_refresh(True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with Guard() as guard:
|
||||||
|
MultisigTest().run_test()
|
282
tests/functional_tests/proofs.py
Executable file
282
tests/functional_tests/proofs.py
Executable file
|
@ -0,0 +1,282 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test misc proofs (tx key, send, receive, reserve)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class ProofsTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
|
||||||
|
self.create_wallets()
|
||||||
|
txid, tx_key, amount = self.transfer()
|
||||||
|
self.check_tx_key(txid, tx_key, amount)
|
||||||
|
self.check_tx_proof(txid, amount)
|
||||||
|
self.check_reserve_proof()
|
||||||
|
|
||||||
|
def mine(self, address, blocks):
|
||||||
|
print("Mining some blocks")
|
||||||
|
daemon = Daemon()
|
||||||
|
daemon.generateblocks(address, blocks)
|
||||||
|
|
||||||
|
def transfer(self):
|
||||||
|
print('Creating transaction')
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount':123456789000}
|
||||||
|
res = self.wallet[0].transfer([dst], get_tx_key = True)
|
||||||
|
assert len(res.tx_hash) == 64
|
||||||
|
assert len(res.tx_key) == 64
|
||||||
|
daemon = Daemon()
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
return (res.tx_hash, res.tx_key, 123456789000)
|
||||||
|
|
||||||
|
def create_wallets(self):
|
||||||
|
print('Creating wallets')
|
||||||
|
seeds = [
|
||||||
|
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
||||||
|
'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
|
||||||
|
]
|
||||||
|
self.wallet = [None, None]
|
||||||
|
for i in range(2):
|
||||||
|
self.wallet[i] = Wallet(idx = i)
|
||||||
|
try: self.wallet[i].close_wallet()
|
||||||
|
except: pass
|
||||||
|
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
||||||
|
|
||||||
|
def check_tx_key(self, txid, tx_key, amount):
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print('Checking tx key')
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
|
||||||
|
sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
res = self.wallet[0].get_tx_key(txid)
|
||||||
|
assert res.tx_key == tx_key
|
||||||
|
res = self.wallet[0].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address)
|
||||||
|
assert res.received == amount
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
res = self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address)
|
||||||
|
assert res.received == amount
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = sending_address)
|
||||||
|
assert res.received >= 0 # might be change
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: self.wallet[1].check_tx_key(txid = '0' * 64, tx_key = tx_key, address = receiving_address)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
res = self.wallet[1].check_tx_key(txid = txid, tx_key = '0' * 64, address = receiving_address)
|
||||||
|
assert res.received == 0
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
def check_tx_proof(self, txid, amount):
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print('Checking tx proof')
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
|
||||||
|
sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo');
|
||||||
|
assert res.signature.startswith('InProof');
|
||||||
|
signature0i = res.signature
|
||||||
|
res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar');
|
||||||
|
assert res.signature.startswith('OutProof');
|
||||||
|
signature0o = res.signature
|
||||||
|
res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz');
|
||||||
|
assert res.signature.startswith('InProof');
|
||||||
|
signature1 = res.signature
|
||||||
|
|
||||||
|
res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i);
|
||||||
|
assert res.good
|
||||||
|
assert res.received > 0 # likely change
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof('0' * 64, sending_address, 'foo', signature0i);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'foo', signature0i);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, sending_address, '', signature0i);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature1);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
|
||||||
|
res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o);
|
||||||
|
assert res.good
|
||||||
|
assert res.received == amount
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof('0' * 64, receiving_address, 'bar', signature0o);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'bar', signature0o);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, receiving_address, '', signature0o);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0i);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
|
||||||
|
res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1);
|
||||||
|
assert res.good
|
||||||
|
assert res.received == amount
|
||||||
|
assert not res.in_pool
|
||||||
|
assert res.confirmations == 1
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[1].check_tx_proof('0' * 64, receiving_address, 'baz', signature1);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[1].check_tx_proof(txid, sending_address, 'baz', signature1);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[1].check_tx_proof(txid, receiving_address, '', signature1);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature0o);
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
|
||||||
|
def check_reserve_proof(self):
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print('Checking reserve proof')
|
||||||
|
|
||||||
|
address0 = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
address1 = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
balance0 = res.balance
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
res = self.wallet[1].get_balance()
|
||||||
|
balance1 = res.balance
|
||||||
|
|
||||||
|
res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo')
|
||||||
|
assert res.signature.startswith('ReserveProof')
|
||||||
|
signature = res.signature
|
||||||
|
for i in range(2):
|
||||||
|
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
|
||||||
|
assert res.good
|
||||||
|
assert res.total == balance0
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature)
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature)
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
amount = int(balance0 / 10)
|
||||||
|
res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo')
|
||||||
|
assert res.signature.startswith('ReserveProof')
|
||||||
|
signature = res.signature
|
||||||
|
for i in range(2):
|
||||||
|
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
|
||||||
|
assert res.good
|
||||||
|
assert res.total >= amount and res.total <= balance0
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature)
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature)
|
||||||
|
except: ok = True
|
||||||
|
assert ok or not res.good
|
||||||
|
|
||||||
|
ok = False
|
||||||
|
try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo')
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
|
||||||
|
class Guard:
|
||||||
|
def __enter__(self):
|
||||||
|
for i in range(4):
|
||||||
|
Wallet(idx = i).auto_refresh(False)
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
for i in range(4):
|
||||||
|
Wallet(idx = i).auto_refresh(True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with Guard() as guard:
|
||||||
|
ProofsTest().run_test()
|
85
tests/functional_tests/sign_message.py
Executable file
85
tests/functional_tests/sign_message.py
Executable file
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test message signing/verification RPC calls
|
||||||
|
|
||||||
|
Test the following RPCs:
|
||||||
|
- sign
|
||||||
|
- verify
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class MessageSigningTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.check_signing()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallets'
|
||||||
|
seeds = [
|
||||||
|
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
||||||
|
'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
|
||||||
|
]
|
||||||
|
self.address = [
|
||||||
|
'42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm',
|
||||||
|
'44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW',
|
||||||
|
]
|
||||||
|
self.wallet = [None, None]
|
||||||
|
for i in range(2):
|
||||||
|
self.wallet[i] = Wallet(idx = i)
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: self.wallet[i].close_wallet()
|
||||||
|
except: pass
|
||||||
|
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
||||||
|
assert res.address == self.address[i]
|
||||||
|
assert res.seed == seeds[i]
|
||||||
|
|
||||||
|
def check_signing(self):
|
||||||
|
print 'Signing/verifing messages'
|
||||||
|
messages = ['foo', '']
|
||||||
|
for message in messages:
|
||||||
|
res = self.wallet[0].sign(message)
|
||||||
|
signature = res.signature
|
||||||
|
for i in range(2):
|
||||||
|
res = self.wallet[i].verify(message, self.address[0], signature)
|
||||||
|
assert res.good
|
||||||
|
res = self.wallet[i].verify('different', self.address[0], signature)
|
||||||
|
assert not res.good
|
||||||
|
res = self.wallet[i].verify(message, self.address[1], signature)
|
||||||
|
assert not res.good
|
||||||
|
res = self.wallet[i].verify(message, self.address[0], signature + 'x')
|
||||||
|
assert not res.good
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
MessageSigningTest().run_test()
|
|
@ -42,8 +42,8 @@ import time
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from test_framework.daemon import Daemon
|
from framework.daemon import Daemon
|
||||||
from test_framework.wallet import Wallet
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
|
||||||
class SpeedTest():
|
class SpeedTest():
|
||||||
|
@ -58,7 +58,7 @@ class SpeedTest():
|
||||||
|
|
||||||
self._test_speed_generateblocks(daemon=daemon, blocks=70)
|
self._test_speed_generateblocks(daemon=daemon, blocks=70)
|
||||||
for i in range(1, 10):
|
for i in range(1, 10):
|
||||||
while wallet.get_balance()['unlocked_balance'] == 0:
|
while wallet.get_balance().unlocked_balance == 0:
|
||||||
print('Waiting for wallet to refresh...')
|
print('Waiting for wallet to refresh...')
|
||||||
sleep(1)
|
sleep(1)
|
||||||
self._test_speed_transfer_split(wallet=wallet)
|
self._test_speed_transfer_split(wallet=wallet)
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
# Copyright (c) 2018 The Monero Project
|
|
||||||
#
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification, are
|
|
||||||
# permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
||||||
# conditions and the following disclaimer.
|
|
||||||
#
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
||||||
# of conditions and the following disclaimer in the documentation and/or other
|
|
||||||
# materials provided with the distribution.
|
|
||||||
#
|
|
||||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
||||||
# used to endorse or promote products derived from this software without specific
|
|
||||||
# prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
||||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
||||||
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
||||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
||||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
"""Daemon class to make rpc calls and store state."""
|
|
||||||
|
|
||||||
from .rpc import JSONRPC
|
|
||||||
|
|
||||||
class Daemon(object):
|
|
||||||
|
|
||||||
def __init__(self, protocol='http', host='127.0.0.1', port=18081, path='/json_rpc'):
|
|
||||||
self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
|
|
||||||
|
|
||||||
def getblocktemplate(self, address):
|
|
||||||
getblocktemplate = {
|
|
||||||
'method': 'getblocktemplate',
|
|
||||||
'params': {
|
|
||||||
'wallet_address': address,
|
|
||||||
'reserve_size' : 1
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(getblocktemplate)
|
|
||||||
|
|
||||||
def submitblock(self, block):
|
|
||||||
submitblock = {
|
|
||||||
'method': 'submitblock',
|
|
||||||
'params': [ block ],
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(submitblock)
|
|
||||||
|
|
||||||
def getblock(self, height=0):
|
|
||||||
getblock = {
|
|
||||||
'method': 'getblock',
|
|
||||||
'params': {
|
|
||||||
'height': height
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(getblock)
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
get_connections = {
|
|
||||||
'method': 'get_connections',
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(get_connections)
|
|
||||||
|
|
||||||
def get_info(self):
|
|
||||||
get_info = {
|
|
||||||
'method': 'get_info',
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(get_info)
|
|
||||||
|
|
||||||
def hard_fork_info(self):
|
|
||||||
hard_fork_info = {
|
|
||||||
'method': 'hard_fork_info',
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(hard_fork_info)
|
|
||||||
|
|
||||||
def generateblocks(self, address, blocks=1):
|
|
||||||
generateblocks = {
|
|
||||||
'method': 'generateblocks',
|
|
||||||
'params': {
|
|
||||||
'amount_of_blocks' : blocks,
|
|
||||||
'reserve_size' : 20,
|
|
||||||
'wallet_address': address
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(generateblocks)
|
|
|
@ -1,120 +0,0 @@
|
||||||
# Copyright (c) 2018 The Monero Project
|
|
||||||
#
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification, are
|
|
||||||
# permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
||||||
# conditions and the following disclaimer.
|
|
||||||
#
|
|
||||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
||||||
# of conditions and the following disclaimer in the documentation and/or other
|
|
||||||
# materials provided with the distribution.
|
|
||||||
#
|
|
||||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
||||||
# used to endorse or promote products derived from this software without specific
|
|
||||||
# prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
||||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
||||||
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
||||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
||||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
"""Daemon class to make rpc calls and store state."""
|
|
||||||
|
|
||||||
from .rpc import JSONRPC
|
|
||||||
|
|
||||||
class Wallet(object):
|
|
||||||
|
|
||||||
def __init__(self, protocol='http', host='127.0.0.1', port=18083, path='/json_rpc'):
|
|
||||||
self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
|
|
||||||
|
|
||||||
def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1):
|
|
||||||
destinations = []
|
|
||||||
for i in range(transfer_number_of_destinations):
|
|
||||||
destinations.append({"amount":transfer_amount,"address":address})
|
|
||||||
return destinations
|
|
||||||
|
|
||||||
def make_destinations(self, addresses, transfer_amounts):
|
|
||||||
destinations = []
|
|
||||||
for i in range(len(addresses)):
|
|
||||||
destinations.append({'amount':transfer_amounts[i],'address':addresses[i]})
|
|
||||||
return destinations
|
|
||||||
|
|
||||||
def transfer(self, destinations, ringsize=7, payment_id=''):
|
|
||||||
transfer = {
|
|
||||||
'method': 'transfer',
|
|
||||||
'params': {
|
|
||||||
'destinations': destinations,
|
|
||||||
'mixin' : ringsize - 1,
|
|
||||||
'get_tx_key' : True
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
if(len(payment_id) > 0):
|
|
||||||
transfer['params'].update({'payment_id' : payment_id})
|
|
||||||
return self.rpc.send_request(transfer)
|
|
||||||
|
|
||||||
def transfer_split(self, destinations, ringsize=7, payment_id=''):
|
|
||||||
print(destinations)
|
|
||||||
transfer = {
|
|
||||||
"method": "transfer_split",
|
|
||||||
"params": {
|
|
||||||
"destinations": destinations,
|
|
||||||
"mixin" : ringsize - 1,
|
|
||||||
"get_tx_key" : True,
|
|
||||||
"new_algorithm" : True
|
|
||||||
},
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": "0"
|
|
||||||
}
|
|
||||||
if(len(payment_id) > 0):
|
|
||||||
transfer['params'].update({'payment_id' : payment_id})
|
|
||||||
return self.rpc.send_request(transfer)
|
|
||||||
|
|
||||||
def create_wallet(self, index=''):
|
|
||||||
create_wallet = {
|
|
||||||
'method': 'create_wallet',
|
|
||||||
'params': {
|
|
||||||
'filename': 'testWallet' + index,
|
|
||||||
'password' : '',
|
|
||||||
'language' : 'English'
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(create_wallet)
|
|
||||||
|
|
||||||
def get_balance(self):
|
|
||||||
get_balance = {
|
|
||||||
'method': 'get_balance',
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(get_balance)
|
|
||||||
|
|
||||||
def sweep_dust(self):
|
|
||||||
sweep_dust = {
|
|
||||||
'method': 'sweep_dust',
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(sweep_dust)
|
|
||||||
|
|
||||||
def sweep_all(self, address):
|
|
||||||
sweep_all = {
|
|
||||||
'method': 'sweep_all',
|
|
||||||
'params' : {
|
|
||||||
'address' : ''
|
|
||||||
},
|
|
||||||
'jsonrpc': '2.0',
|
|
||||||
'id': '0'
|
|
||||||
}
|
|
||||||
return self.rpc.send_request(sweep_all)
|
|
487
tests/functional_tests/transfer.py
Executable file
487
tests/functional_tests/transfer.py
Executable file
|
@ -0,0 +1,487 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test simple transfers
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class TransferTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.mine()
|
||||||
|
self.transfer()
|
||||||
|
self.check_get_bulk_payments()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallets'
|
||||||
|
seeds = [
|
||||||
|
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
||||||
|
'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
|
||||||
|
'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid',
|
||||||
|
]
|
||||||
|
self.wallet = [None] * len(seeds)
|
||||||
|
for i in range(len(seeds)):
|
||||||
|
self.wallet[i] = Wallet(idx = i)
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: self.wallet[i].close_wallet()
|
||||||
|
except: pass
|
||||||
|
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
||||||
|
|
||||||
|
def mine(self):
|
||||||
|
print("Mining some blocks")
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
|
||||||
|
for i in range(len(self.wallet)):
|
||||||
|
self.wallet[i].refresh()
|
||||||
|
|
||||||
|
def transfer(self):
|
||||||
|
daemon = Daemon()
|
||||||
|
|
||||||
|
print("Creating transfer to self")
|
||||||
|
|
||||||
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
||||||
|
payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'
|
||||||
|
|
||||||
|
start_balances = [0] * len(self.wallet)
|
||||||
|
running_balances = [0] * len(self.wallet)
|
||||||
|
for i in range(len(self.wallet)):
|
||||||
|
res = self.wallet[i].get_balance()
|
||||||
|
start_balances[i] = res.balance
|
||||||
|
running_balances[i] = res.balance
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
if i == 0:
|
||||||
|
assert res.blocks_to_unlock == 59 # we've been mining to it
|
||||||
|
else:
|
||||||
|
assert res.blocks_to_unlock == 0
|
||||||
|
|
||||||
|
print ('Checking short payment IDs cannot be used when not in an integrated address')
|
||||||
|
ok = False
|
||||||
|
try: self.wallet[0].transfer([dst], ring_size = 11, payment_id = '1234567812345678', get_tx_key = False)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
print ('Checking empty destination is rejected')
|
||||||
|
ok = False
|
||||||
|
try: self.wallet[0].transfer([], ring_size = 11, get_tx_key = False)
|
||||||
|
except: ok = True
|
||||||
|
assert ok
|
||||||
|
|
||||||
|
res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False)
|
||||||
|
assert len(res.tx_hash) == 32*2
|
||||||
|
txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 0
|
||||||
|
assert res.amount > 0
|
||||||
|
amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) == 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) == 0
|
||||||
|
assert len(res.unsigned_txset) == 0
|
||||||
|
unsigned_txset = res.unsigned_txset
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
|
res = daemon.get_info()
|
||||||
|
height = res.height
|
||||||
|
|
||||||
|
res = self.wallet[0].get_transfers()
|
||||||
|
assert len(res['in']) == height - 1 # coinbases
|
||||||
|
assert not 'out' in res or len(res.out) == 0 # not mined yet
|
||||||
|
assert len(res.pending) == 1
|
||||||
|
assert not 'pool' in res or len(res.pool) == 0
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
for e in res['in']:
|
||||||
|
assert e.type == 'block'
|
||||||
|
e = res.pending[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'pending'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 0
|
||||||
|
|
||||||
|
running_balances[0] -= 1000000000000 + fee
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.getlastblockheader()
|
||||||
|
running_balances[0] += res.block_header.reward
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
|
running_balances[0] += 1000000000000
|
||||||
|
|
||||||
|
res = self.wallet[0].get_transfers()
|
||||||
|
assert len(res['in']) == height # coinbases
|
||||||
|
assert len(res.out) == 1 # not mined yet
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert not 'pool' in res or len(res.pool) == 0
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
for e in res['in']:
|
||||||
|
assert e.type == 'block'
|
||||||
|
e = res.out[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'out'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 1
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
print("Creating transfer to another, manual relay")
|
||||||
|
|
||||||
|
dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000}
|
||||||
|
res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True, do_not_relay = True, get_tx_hex = True)
|
||||||
|
assert len(res.tx_hash) == 32*2
|
||||||
|
txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 32*2
|
||||||
|
assert res.amount == 1000000000000
|
||||||
|
amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) > 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) == 0
|
||||||
|
assert len(res.unsigned_txset) == 0
|
||||||
|
tx_blob = res.tx_blob
|
||||||
|
|
||||||
|
res = daemon.send_raw_transaction(tx_blob)
|
||||||
|
assert res.not_relayed == False
|
||||||
|
assert res.low_mixin == False
|
||||||
|
assert res.double_spend == False
|
||||||
|
assert res.invalid_input == False
|
||||||
|
assert res.invalid_output == False
|
||||||
|
assert res.too_big == False
|
||||||
|
assert res.overspend == False
|
||||||
|
assert res.fee_too_low == False
|
||||||
|
assert res.not_rct == False
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
|
||||||
|
res = self.wallet[1].get_transfers()
|
||||||
|
assert not 'in' in res or len(res['in']) == 0
|
||||||
|
assert not 'out' in res or len(res.out) == 0
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert len(res.pool) == 1
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
e = res.pool[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'pool'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 0
|
||||||
|
assert e.amount == amount
|
||||||
|
assert e.fee == fee
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.getlastblockheader()
|
||||||
|
running_balances[0] -= 1000000000000 + fee
|
||||||
|
running_balances[0] += res.block_header.reward
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
running_balances[1] += 1000000000000
|
||||||
|
|
||||||
|
res = self.wallet[1].get_transfers()
|
||||||
|
assert len(res['in']) == 1
|
||||||
|
assert not 'out' in res or len(res.out) == 0
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert not 'pool' in res or len(res.pool) == 0
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
e = res['in'][0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'in'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 1
|
||||||
|
assert e.amount == amount
|
||||||
|
assert e.fee == fee
|
||||||
|
|
||||||
|
res = self.wallet[1].get_balance()
|
||||||
|
assert res.balance == running_balances[1]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 9
|
||||||
|
|
||||||
|
print 'Creating multi out transfer'
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
|
dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
||||||
|
dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000}
|
||||||
|
dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000}
|
||||||
|
res = self.wallet[0].transfer([dst0, dst1, dst2], ring_size = 11, payment_id = payment_id, get_tx_key = True)
|
||||||
|
assert len(res.tx_hash) == 32*2
|
||||||
|
txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 32*2
|
||||||
|
assert res.amount == 1000000000000 + 1100000000000 + 1200000000000
|
||||||
|
amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) == 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) == 0
|
||||||
|
assert len(res.unsigned_txset) == 0
|
||||||
|
unsigned_txset = res.unsigned_txset
|
||||||
|
|
||||||
|
running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + fee
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.getlastblockheader()
|
||||||
|
running_balances[0] += res.block_header.reward
|
||||||
|
running_balances[0] += 1000000000000
|
||||||
|
running_balances[1] += 1100000000000
|
||||||
|
running_balances[2] += 1200000000000
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
|
res = self.wallet[0].get_transfers()
|
||||||
|
assert len(res['in']) == height + 2
|
||||||
|
assert len(res.out) == 3
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert not 'pool' in res or len(res.pool) == 1
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
e = [o for o in res.out if o.txid == txid]
|
||||||
|
assert len(e) == 1
|
||||||
|
e = e[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'out'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 1
|
||||||
|
|
||||||
|
assert e.amount == amount
|
||||||
|
assert e.fee == fee
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
res = self.wallet[1].get_transfers()
|
||||||
|
assert len(res['in']) == 2
|
||||||
|
assert not 'out' in res or len(res.out) == 0
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert not 'pool' in res or len(res.pool) == 0
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
e = [o for o in res['in'] if o.txid == txid]
|
||||||
|
assert len(e) == 1
|
||||||
|
e = e[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'in'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 1
|
||||||
|
assert e.amount == 1100000000000
|
||||||
|
assert e.fee == fee
|
||||||
|
|
||||||
|
res = self.wallet[1].get_balance()
|
||||||
|
assert res.balance == running_balances[1]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 9
|
||||||
|
|
||||||
|
self.wallet[2].refresh()
|
||||||
|
res = self.wallet[2].get_transfers()
|
||||||
|
assert len(res['in']) == 1
|
||||||
|
assert not 'out' in res or len(res.out) == 0
|
||||||
|
assert not 'pending' in res or len(res.pending) == 0
|
||||||
|
assert not 'pool' in res or len(res.pool) == 0
|
||||||
|
assert not 'failed' in res or len(res.failed) == 0
|
||||||
|
e = [o for o in res['in'] if o.txid == txid]
|
||||||
|
assert len(e) == 1
|
||||||
|
e = e[0]
|
||||||
|
assert e.txid == txid
|
||||||
|
assert e.payment_id == payment_id
|
||||||
|
assert e.type == 'in'
|
||||||
|
assert e.unlock_time == 0
|
||||||
|
assert e.subaddr_index.major == 0
|
||||||
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
||||||
|
assert e.address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK'
|
||||||
|
assert e.double_spend_seen == False
|
||||||
|
assert e.confirmations == 1
|
||||||
|
assert e.amount == 1200000000000
|
||||||
|
assert e.fee == fee
|
||||||
|
|
||||||
|
res = self.wallet[2].get_balance()
|
||||||
|
assert res.balance == running_balances[2]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 9
|
||||||
|
|
||||||
|
print('Sending to integrated address')
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
i_pid = '1111111122222222'
|
||||||
|
res = self.wallet[0].make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid)
|
||||||
|
i_address = res.integrated_address
|
||||||
|
res = self.wallet[0].transfer([{'address': i_address, 'amount': 200000000}])
|
||||||
|
assert len(res.tx_hash) == 32*2
|
||||||
|
i_txid = res.tx_hash
|
||||||
|
assert len(res.tx_key) == 32*2
|
||||||
|
assert res.amount == 200000000
|
||||||
|
i_amount = res.amount
|
||||||
|
assert res.fee > 0
|
||||||
|
fee = res.fee
|
||||||
|
assert len(res.tx_blob) == 0
|
||||||
|
assert len(res.tx_metadata) == 0
|
||||||
|
assert len(res.multisig_txset) == 0
|
||||||
|
assert len(res.unsigned_txset) == 0
|
||||||
|
|
||||||
|
running_balances[0] -= 200000000 + fee
|
||||||
|
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.getlastblockheader()
|
||||||
|
running_balances[0] += res.block_header.reward
|
||||||
|
running_balances[1] += 200000000
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
res = self.wallet[1].get_balance()
|
||||||
|
assert res.balance == running_balances[1]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 9
|
||||||
|
|
||||||
|
self.wallet[2].refresh()
|
||||||
|
res = self.wallet[2].get_balance()
|
||||||
|
assert res.balance == running_balances[2]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 8
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.getlastblockheader()
|
||||||
|
running_balances[0] += res.block_header.reward
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
res = self.wallet[0].get_balance()
|
||||||
|
assert res.balance == running_balances[0]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 59
|
||||||
|
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
res = self.wallet[1].get_balance()
|
||||||
|
assert res.balance == running_balances[1]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 8
|
||||||
|
|
||||||
|
self.wallet[2].refresh()
|
||||||
|
res = self.wallet[2].get_balance()
|
||||||
|
assert res.balance == running_balances[2]
|
||||||
|
assert res.unlocked_balance <= res.balance
|
||||||
|
assert res.blocks_to_unlock == 7
|
||||||
|
|
||||||
|
|
||||||
|
def check_get_bulk_payments(self):
|
||||||
|
print('Checking get_bulk_payments')
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
res = daemon.get_info()
|
||||||
|
height = res.height
|
||||||
|
|
||||||
|
self.wallet[0].refresh()
|
||||||
|
res = self.wallet[0].get_bulk_payments()
|
||||||
|
assert len(res.payments) >= 83 # at least 83 coinbases
|
||||||
|
res = self.wallet[0].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
|
||||||
|
assert 'payments' not in res or len(res.payments) == 0
|
||||||
|
res = self.wallet[0].get_bulk_payments(min_block_height = height)
|
||||||
|
assert 'payments' not in res or len(res.payments) == 0
|
||||||
|
res = self.wallet[0].get_bulk_payments(min_block_height = height - 40)
|
||||||
|
assert len(res.payments) >= 39 # coinbases
|
||||||
|
|
||||||
|
self.wallet[1].refresh()
|
||||||
|
res = self.wallet[1].get_bulk_payments()
|
||||||
|
assert len(res.payments) >= 3 # two txes to standard address were sent, plus one to integrated address
|
||||||
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
|
||||||
|
assert len(res.payments) >= 2 # two txes were sent with that payment id
|
||||||
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'])
|
||||||
|
assert 'payments' not in res or len(res.payments) == 0 # none with that payment id
|
||||||
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48])
|
||||||
|
assert len(res.payments) >= 1 # one tx to integrated address
|
||||||
|
|
||||||
|
self.wallet[2].refresh()
|
||||||
|
res = self.wallet[2].get_bulk_payments()
|
||||||
|
assert len(res.payments) >= 1 # one tx was sent
|
||||||
|
res = self.wallet[2].get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64])
|
||||||
|
assert len(res.payments) >= 1 # one tx was sent
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
TransferTest().run_test()
|
156
tests/functional_tests/txpool.py
Executable file
156
tests/functional_tests/txpool.py
Executable file
|
@ -0,0 +1,156 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test txpool
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.daemon import Daemon
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class TransferTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.mine()
|
||||||
|
self.check_txpool()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallet'
|
||||||
|
wallet = Wallet()
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
||||||
|
res = wallet.restore_deterministic_wallet(seed = seed)
|
||||||
|
|
||||||
|
def mine(self):
|
||||||
|
print("Mining some blocks")
|
||||||
|
daemon = Daemon()
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
|
||||||
|
wallet.refresh()
|
||||||
|
|
||||||
|
def create_txes(self, address, ntxes):
|
||||||
|
print('Creating ' + str(ntxes) + ' transactions')
|
||||||
|
|
||||||
|
daemon = Daemon()
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
dst = {'address': address, 'amount': 1000000000000}
|
||||||
|
|
||||||
|
txes = {}
|
||||||
|
for i in range(ntxes):
|
||||||
|
res = wallet.transfer([dst], get_tx_hex = True)
|
||||||
|
txes[res.tx_hash] = res
|
||||||
|
|
||||||
|
return txes
|
||||||
|
|
||||||
|
def check_txpool(self):
|
||||||
|
daemon = Daemon()
|
||||||
|
wallet = Wallet()
|
||||||
|
|
||||||
|
res = daemon.get_info()
|
||||||
|
height = res.height
|
||||||
|
txpool_size = res.tx_pool_size
|
||||||
|
|
||||||
|
txes = self.create_txes('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 5)
|
||||||
|
|
||||||
|
res = daemon.get_info()
|
||||||
|
assert res.tx_pool_size == txpool_size + 5
|
||||||
|
txpool_size = res.tx_pool_size
|
||||||
|
|
||||||
|
res = daemon.get_transaction_pool()
|
||||||
|
assert len(res.transactions) == txpool_size
|
||||||
|
for txid in txes.keys():
|
||||||
|
x = [x for x in res.transactions if x.id_hash == txid]
|
||||||
|
assert len(x) == 1
|
||||||
|
x = x[0]
|
||||||
|
assert x.kept_by_block == False
|
||||||
|
assert x.last_failed_id_hash == '0'*64
|
||||||
|
assert x.double_spend_seen == False
|
||||||
|
assert x.weight >= x.blob_size
|
||||||
|
|
||||||
|
assert x.blob_size * 2 == len(txes[txid].tx_blob)
|
||||||
|
assert x.fee == txes[txid].fee
|
||||||
|
assert x.tx_blob == txes[txid].tx_blob
|
||||||
|
|
||||||
|
res = daemon.get_transaction_pool_hashes()
|
||||||
|
assert sorted(res.tx_hashes) == sorted(txes.keys())
|
||||||
|
|
||||||
|
print('Flushing 2 transactions')
|
||||||
|
daemon.flush_txpool([txes.keys()[1], txes.keys()[3]])
|
||||||
|
res = daemon.get_transaction_pool()
|
||||||
|
assert len(res.transactions) == txpool_size - 2
|
||||||
|
assert len([x for x in res.transactions if x.id_hash == txes.keys()[1]]) == 0
|
||||||
|
assert len([x for x in res.transactions if x.id_hash == txes.keys()[3]]) == 0
|
||||||
|
|
||||||
|
new_keys = txes.keys()
|
||||||
|
new_keys.remove(txes.keys()[1])
|
||||||
|
new_keys.remove(txes.keys()[3])
|
||||||
|
res = daemon.get_transaction_pool_hashes()
|
||||||
|
assert sorted(res.tx_hashes) == sorted(new_keys)
|
||||||
|
|
||||||
|
print('Flushing unknown transactions')
|
||||||
|
unknown_txids = ['1'*64, '2'*64, '3'*64]
|
||||||
|
daemon.flush_txpool(unknown_txids)
|
||||||
|
res = daemon.get_transaction_pool()
|
||||||
|
assert len(res.transactions) == txpool_size - 2
|
||||||
|
|
||||||
|
print('Mining transactions')
|
||||||
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
||||||
|
res = daemon.get_transaction_pool()
|
||||||
|
assert not 'transactions' in res or len(res.transactions) == txpool_size - 5
|
||||||
|
res = daemon.get_transaction_pool_hashes()
|
||||||
|
assert not 'tx_hashes' in res or len(res.tx_hashes) == 0
|
||||||
|
|
||||||
|
print('Popping block')
|
||||||
|
daemon.pop_blocks(1)
|
||||||
|
res = daemon.get_transaction_pool_hashes()
|
||||||
|
assert sorted(res.tx_hashes) == sorted(new_keys)
|
||||||
|
res = daemon.get_transaction_pool()
|
||||||
|
assert len(res.transactions) == txpool_size - 2
|
||||||
|
for txid in new_keys:
|
||||||
|
x = [x for x in res.transactions if x.id_hash == txid]
|
||||||
|
assert len(x) == 1
|
||||||
|
x = x[0]
|
||||||
|
assert x.kept_by_block == True
|
||||||
|
assert x.last_failed_id_hash == '0'*64
|
||||||
|
assert x.double_spend_seen == False
|
||||||
|
assert x.weight >= x.blob_size
|
||||||
|
|
||||||
|
assert x.blob_size * 2 == len(txes[txid].tx_blob)
|
||||||
|
assert x.fee == txes[txid].fee
|
||||||
|
assert x.tx_blob == txes[txid].tx_blob
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
TransferTest().run_test()
|
152
tests/functional_tests/wallet_address.py
Executable file
152
tests/functional_tests/wallet_address.py
Executable file
|
@ -0,0 +1,152 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2019 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""Test transaction creation RPC calls
|
||||||
|
|
||||||
|
Test the following RPCs:
|
||||||
|
- [TODO: many tests still need to be written]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from framework.wallet import Wallet
|
||||||
|
|
||||||
|
class WalletAddressTest():
|
||||||
|
def run_test(self):
|
||||||
|
self.create()
|
||||||
|
self.check_main_address()
|
||||||
|
self.check_keys()
|
||||||
|
self.create_subaddresses()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
print 'Creating wallet'
|
||||||
|
wallet = Wallet()
|
||||||
|
# close the wallet if any, will throw if none is loaded
|
||||||
|
try: wallet.close_wallet()
|
||||||
|
except: pass
|
||||||
|
seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
||||||
|
res = wallet.restore_deterministic_wallet(seed = seed)
|
||||||
|
assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
||||||
|
assert res.seed == seed
|
||||||
|
|
||||||
|
def check_main_address(self):
|
||||||
|
print 'Getting address'
|
||||||
|
wallet = Wallet()
|
||||||
|
res = wallet.get_address()
|
||||||
|
assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == res.address
|
||||||
|
assert res.addresses[0].address_index == 0
|
||||||
|
assert res.addresses[0].used == False
|
||||||
|
|
||||||
|
def check_keys(self):
|
||||||
|
print 'Checking keys'
|
||||||
|
wallet = Wallet()
|
||||||
|
res = wallet.query_key('view_key')
|
||||||
|
assert res.key == '49774391fa5e8d249fc2c5b45dadef13534bf2483dede880dac88f061e809100'
|
||||||
|
res = wallet.query_key('spend_key')
|
||||||
|
assert res.key == '148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e'
|
||||||
|
res = wallet.query_key('mnemonic')
|
||||||
|
assert res.key == 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
|
||||||
|
|
||||||
|
def create_subaddresses(self):
|
||||||
|
print 'Creating subaddresses'
|
||||||
|
wallet = Wallet()
|
||||||
|
res = wallet.create_account("idx1")
|
||||||
|
assert res.account_index == 1, res
|
||||||
|
assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res
|
||||||
|
res = wallet.create_account("idx2")
|
||||||
|
assert res.account_index == 2, res
|
||||||
|
assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res
|
||||||
|
|
||||||
|
res = wallet.get_address(0, 0)
|
||||||
|
assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address_index == 0, res
|
||||||
|
res = wallet.get_address(1, 0)
|
||||||
|
assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].label == 'idx1', res
|
||||||
|
assert res.addresses[0].address_index == 0, res
|
||||||
|
res = wallet.get_address(2, 0)
|
||||||
|
assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].label == 'idx2', res
|
||||||
|
assert res.addresses[0].address_index == 0, res
|
||||||
|
|
||||||
|
res = wallet.create_address(0, "sub_0_1")
|
||||||
|
res = wallet.create_address(1, "sub_1_1")
|
||||||
|
res = wallet.create_address(1, "sub_1_2")
|
||||||
|
|
||||||
|
res = wallet.get_address(0, [1])
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == '84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF'
|
||||||
|
assert res.addresses[0].label == 'sub_0_1'
|
||||||
|
res = wallet.get_address(1, [1])
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4'
|
||||||
|
assert res.addresses[0].label == 'sub_1_1'
|
||||||
|
res = wallet.get_address(1, [2])
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
|
||||||
|
assert res.addresses[0].label == 'sub_1_2'
|
||||||
|
res = wallet.get_address(1, [0, 1, 2])
|
||||||
|
assert len(res.addresses) == 3
|
||||||
|
assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf'
|
||||||
|
assert res.addresses[0].label == 'idx1'
|
||||||
|
assert res.addresses[1].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4'
|
||||||
|
assert res.addresses[1].label == 'sub_1_1'
|
||||||
|
assert res.addresses[2].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
|
||||||
|
assert res.addresses[2].label == 'sub_1_2'
|
||||||
|
|
||||||
|
res = wallet.label_address((1, 2), "sub_1_2_new")
|
||||||
|
res = wallet.get_address(1, [2])
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
|
||||||
|
assert res.addresses[0].label == 'sub_1_2_new'
|
||||||
|
|
||||||
|
res = wallet.label_account(1, "idx1_new")
|
||||||
|
res = wallet.get_address(1, [0])
|
||||||
|
assert len(res.addresses) == 1
|
||||||
|
assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf'
|
||||||
|
assert res.addresses[0].label == 'idx1_new'
|
||||||
|
|
||||||
|
res = wallet.get_address_index('87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB')
|
||||||
|
assert res.index == {'major': 1, 'minor': 2}
|
||||||
|
res = wallet.get_address_index('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
|
||||||
|
assert res.index == {'major': 0, 'minor': 0}
|
||||||
|
res = wallet.get_address_index('84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF')
|
||||||
|
assert res.index == {'major': 0, 'minor': 1}
|
||||||
|
res = wallet.get_address_index('82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf')
|
||||||
|
assert res.index == {'major': 1, 'minor': 0}
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WalletAddressTest().run_test()
|
49
utils/python-rpc/console.py
Executable file
49
utils/python-rpc/console.py
Executable file
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import socket
|
||||||
|
from framework import rpc
|
||||||
|
from framework import wallet
|
||||||
|
from framework import daemon
|
||||||
|
|
||||||
|
USAGE = 'usage: python -i console.py <port>'
|
||||||
|
try:
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
except:
|
||||||
|
print(USAGE)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# check for open port
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(1)
|
||||||
|
if s.connect_ex(('127.0.0.1', port)) != 0:
|
||||||
|
print('No wallet or daemon RPC on port ' + str(port))
|
||||||
|
sys.exit(1)
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
# both wallet and daemon have a get_version JSON RPC
|
||||||
|
rpc = rpc.JSONRPC('{protocol}://{host}:{port}'.format(protocol='http', host='127.0.0.1', port=port))
|
||||||
|
get_version = {
|
||||||
|
'method': 'get_version',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
res = rpc.send_json_rpc_request(get_version)
|
||||||
|
except Exception, e:
|
||||||
|
print('Failed to call version RPC: ' + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if 'version' not in res:
|
||||||
|
print('Server is not a monero process')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if 'status' in res:
|
||||||
|
rpc = daemon.Daemon(port=port)
|
||||||
|
else:
|
||||||
|
rpc = wallet.Wallet(port=port)
|
||||||
|
|
||||||
|
print('Connected to %s RPC on port %u' % ('daemon' if 'status' in res else 'wallet', port))
|
||||||
|
print('The \'rpc\' object may now be used to use the API')
|
219
utils/python-rpc/framework/daemon.py
Normal file
219
utils/python-rpc/framework/daemon.py
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
# Copyright (c) 2018 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""Daemon class to make rpc calls and store state."""
|
||||||
|
|
||||||
|
from .rpc import JSONRPC
|
||||||
|
|
||||||
|
class Daemon(object):
|
||||||
|
|
||||||
|
def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0):
|
||||||
|
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx))
|
||||||
|
|
||||||
|
def getblocktemplate(self, address):
|
||||||
|
getblocktemplate = {
|
||||||
|
'method': 'getblocktemplate',
|
||||||
|
'params': {
|
||||||
|
'wallet_address': address,
|
||||||
|
'reserve_size' : 1
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getblocktemplate)
|
||||||
|
|
||||||
|
def send_raw_transaction(self, tx_as_hex, do_not_relay = False):
|
||||||
|
send_raw_transaction = {
|
||||||
|
'tx_as_hex': tx_as_hex,
|
||||||
|
'do_not_relay': do_not_relay,
|
||||||
|
}
|
||||||
|
return self.rpc.send_request("/send_raw_transaction", send_raw_transaction)
|
||||||
|
|
||||||
|
def submitblock(self, block):
|
||||||
|
submitblock = {
|
||||||
|
'method': 'submitblock',
|
||||||
|
'params': [ block ],
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(submitblock)
|
||||||
|
|
||||||
|
def getblock(self, height=0):
|
||||||
|
getblock = {
|
||||||
|
'method': 'getblock',
|
||||||
|
'params': {
|
||||||
|
'height': height
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getblock)
|
||||||
|
|
||||||
|
def getlastblockheader(self):
|
||||||
|
getlastblockheader = {
|
||||||
|
'method': 'getlastblockheader',
|
||||||
|
'params': {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getlastblockheader)
|
||||||
|
|
||||||
|
def getblockheaderbyhash(self, hash):
|
||||||
|
getblockheaderbyhash = {
|
||||||
|
'method': 'getblockheaderbyhash',
|
||||||
|
'params': {
|
||||||
|
'hash': hash,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getblockheaderbyhash)
|
||||||
|
|
||||||
|
def getblockheaderbyheight(self, height):
|
||||||
|
getblockheaderbyheight = {
|
||||||
|
'method': 'getblockheaderbyheight',
|
||||||
|
'params': {
|
||||||
|
'height': height,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getblockheaderbyheight)
|
||||||
|
|
||||||
|
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False):
|
||||||
|
getblockheadersrange = {
|
||||||
|
'method': 'getblockheadersrange',
|
||||||
|
'params': {
|
||||||
|
'start_height': start_height,
|
||||||
|
'end_height': end_height,
|
||||||
|
'fill_pow_hash': fill_pow_hash,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(getblockheadersrange)
|
||||||
|
|
||||||
|
def get_connections(self):
|
||||||
|
get_connections = {
|
||||||
|
'method': 'get_connections',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_connections)
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
get_info = {
|
||||||
|
'method': 'get_info',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_info)
|
||||||
|
|
||||||
|
def hard_fork_info(self):
|
||||||
|
hard_fork_info = {
|
||||||
|
'method': 'hard_fork_info',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(hard_fork_info)
|
||||||
|
|
||||||
|
def generateblocks(self, address, blocks=1):
|
||||||
|
generateblocks = {
|
||||||
|
'method': 'generateblocks',
|
||||||
|
'params': {
|
||||||
|
'amount_of_blocks' : blocks,
|
||||||
|
'reserve_size' : 20,
|
||||||
|
'wallet_address': address
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(generateblocks)
|
||||||
|
|
||||||
|
def get_height(self):
|
||||||
|
get_height = {
|
||||||
|
'method': 'get_height',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_request("/get_height", get_height)
|
||||||
|
|
||||||
|
def pop_blocks(self, nblocks = 1):
|
||||||
|
pop_blocks = {
|
||||||
|
'nblocks' : nblocks,
|
||||||
|
}
|
||||||
|
return self.rpc.send_request("/pop_blocks", pop_blocks)
|
||||||
|
|
||||||
|
def start_mining(self, miner_address, threads_count = 0, do_background_mining = False, ignore_battery = False):
|
||||||
|
start_mining = {
|
||||||
|
'miner_address' : miner_address,
|
||||||
|
'threads_count' : threads_count,
|
||||||
|
'do_background_mining' : do_background_mining,
|
||||||
|
'ignore_battery' : ignore_battery,
|
||||||
|
}
|
||||||
|
return self.rpc.send_request('/start_mining', start_mining)
|
||||||
|
|
||||||
|
def stop_mining(self):
|
||||||
|
stop_mining = {
|
||||||
|
}
|
||||||
|
return self.rpc.send_request('/stop_mining', stop_mining)
|
||||||
|
|
||||||
|
def mining_status(self):
|
||||||
|
mining_status = {
|
||||||
|
}
|
||||||
|
return self.rpc.send_request('/mining_status', mining_status)
|
||||||
|
|
||||||
|
def get_transaction_pool(self):
|
||||||
|
get_transaction_pool = {
|
||||||
|
}
|
||||||
|
return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
|
||||||
|
|
||||||
|
def get_transaction_pool_hashes(self):
|
||||||
|
get_transaction_pool_hashes = {
|
||||||
|
}
|
||||||
|
return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
|
||||||
|
|
||||||
|
def flush_txpool(self, txids = []):
|
||||||
|
flush_txpool = {
|
||||||
|
'method': 'flush_txpool',
|
||||||
|
'params': {
|
||||||
|
'txids': txids
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(flush_txpool)
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
get_version = {
|
||||||
|
'method': 'get_version',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_version)
|
|
@ -29,21 +29,55 @@
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
class Response(dict):
|
||||||
|
def __init__(self, d):
|
||||||
|
for k in d.keys():
|
||||||
|
if type(d[k]) == dict:
|
||||||
|
self[k] = Response(d[k])
|
||||||
|
elif type(d[k]) == list:
|
||||||
|
self[k] = []
|
||||||
|
for i in range(len(d[k])):
|
||||||
|
if type(d[k][i]) == dict:
|
||||||
|
self[k].append(Response(d[k][i]))
|
||||||
|
else:
|
||||||
|
self[k].append(d[k][i])
|
||||||
|
else:
|
||||||
|
self[k] = d[k]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self[key]
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
self[key] = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if type(other) == dict:
|
||||||
|
return self == Response(other)
|
||||||
|
if self.keys() != other.keys():
|
||||||
|
return False
|
||||||
|
for k in self.keys():
|
||||||
|
if self[k] != other[k]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
class JSONRPC(object):
|
class JSONRPC(object):
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
def send_request(self, inputs):
|
def send_request(self, path, inputs, result_field = None):
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
self.url,
|
self.url + path,
|
||||||
data=json.dumps(inputs),
|
data=json.dumps(inputs),
|
||||||
headers={'content-type': 'application/json'})
|
headers={'content-type': 'application/json'})
|
||||||
res = res.json()
|
res = res.json()
|
||||||
|
|
||||||
assert 'error' not in res, res
|
assert 'error' not in res, res
|
||||||
|
|
||||||
return res['result']
|
if result_field:
|
||||||
|
res = res[result_field]
|
||||||
|
|
||||||
|
return Response(res)
|
||||||
|
|
||||||
|
def send_json_rpc_request(self, inputs):
|
||||||
|
return self.send_request("/json_rpc", inputs, 'result')
|
||||||
|
|
||||||
|
|
||||||
|
|
600
utils/python-rpc/framework/wallet.py
Normal file
600
utils/python-rpc/framework/wallet.py
Normal file
|
@ -0,0 +1,600 @@
|
||||||
|
# Copyright (c) 2018 The Monero Project
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
# permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
# conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
# of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
# materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
# used to endorse or promote products derived from this software without specific
|
||||||
|
# prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""Daemon class to make rpc calls and store state."""
|
||||||
|
|
||||||
|
from .rpc import JSONRPC
|
||||||
|
|
||||||
|
class Wallet(object):
|
||||||
|
|
||||||
|
def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0):
|
||||||
|
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18090+idx))
|
||||||
|
|
||||||
|
def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1):
|
||||||
|
destinations = []
|
||||||
|
for i in range(transfer_number_of_destinations):
|
||||||
|
destinations.append({"amount":transfer_amount,"address":address})
|
||||||
|
return destinations
|
||||||
|
|
||||||
|
def make_destinations(self, addresses, transfer_amounts):
|
||||||
|
destinations = []
|
||||||
|
for i in range(len(addresses)):
|
||||||
|
destinations.append({'amount':transfer_amounts[i],'address':addresses[i]})
|
||||||
|
return destinations
|
||||||
|
|
||||||
|
def transfer(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False):
|
||||||
|
transfer = {
|
||||||
|
'method': 'transfer',
|
||||||
|
'params': {
|
||||||
|
'destinations': destinations,
|
||||||
|
'account_index': account_index,
|
||||||
|
'subaddr_indices': subaddr_indices,
|
||||||
|
'priority': priority,
|
||||||
|
'ring_size' : ring_size,
|
||||||
|
'unlock_time' : unlock_time,
|
||||||
|
'payment_id' : payment_id,
|
||||||
|
'get_tx_key' : get_tx_key,
|
||||||
|
'do_not_relay' : do_not_relay,
|
||||||
|
'get_tx_hex' : get_tx_hex,
|
||||||
|
'get_tx_metadata' : get_tx_metadata,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(transfer)
|
||||||
|
|
||||||
|
def transfer_split(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False):
|
||||||
|
transfer = {
|
||||||
|
"method": "transfer_split",
|
||||||
|
"params": {
|
||||||
|
'destinations': destinations,
|
||||||
|
'account_index': account_index,
|
||||||
|
'subaddr_indices': subaddr_indices,
|
||||||
|
'priority': priority,
|
||||||
|
'ring_size' : ring_size,
|
||||||
|
'unlock_time' : unlock_time,
|
||||||
|
'payment_id' : payment_id,
|
||||||
|
'get_tx_key' : get_tx_key,
|
||||||
|
'do_not_relay' : do_not_relay,
|
||||||
|
'get_tx_hex' : get_tx_hex,
|
||||||
|
'get_tx_metadata' : get_tx_metadata,
|
||||||
|
},
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "0"
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(transfer)
|
||||||
|
|
||||||
|
def get_bulk_payments(self, payment_ids = [], min_block_height = 0):
|
||||||
|
get_bulk_payments = {
|
||||||
|
'method': 'get_bulk_payments',
|
||||||
|
'params': {
|
||||||
|
'payment_ids': payment_ids,
|
||||||
|
'min_block_height': min_block_height,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_bulk_payments)
|
||||||
|
|
||||||
|
def describe_transfer(self, unsigned_txset):
|
||||||
|
describe_transfer = {
|
||||||
|
'method': 'describe_transfer',
|
||||||
|
'params': {
|
||||||
|
'unsigned_txset': unsigned_txset,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(describe_transfer)
|
||||||
|
|
||||||
|
def create_wallet(self, index=''):
|
||||||
|
create_wallet = {
|
||||||
|
'method': 'create_wallet',
|
||||||
|
'params': {
|
||||||
|
'filename': 'testWallet' + index,
|
||||||
|
'password' : '',
|
||||||
|
'language' : 'English'
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(create_wallet)
|
||||||
|
|
||||||
|
def get_balance(self, account_index = 0, address_indices = [], all_accounts = False):
|
||||||
|
get_balance = {
|
||||||
|
'method': 'get_balance',
|
||||||
|
'params': {
|
||||||
|
'account_index': account_index,
|
||||||
|
'address_indices': address_indices,
|
||||||
|
'all_accounts': all_accounts,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_balance)
|
||||||
|
|
||||||
|
def sweep_dust(self):
|
||||||
|
sweep_dust = {
|
||||||
|
'method': 'sweep_dust',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(sweep_dust)
|
||||||
|
|
||||||
|
def sweep_all(self, address):
|
||||||
|
sweep_all = {
|
||||||
|
'method': 'sweep_all',
|
||||||
|
'params' : {
|
||||||
|
'address' : ''
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(sweep_all)
|
||||||
|
|
||||||
|
def get_address(self, account_index = 0, subaddresses = []):
|
||||||
|
get_address = {
|
||||||
|
'method': 'get_address',
|
||||||
|
'params' : {
|
||||||
|
'account_index' : account_index,
|
||||||
|
'address_index': subaddresses
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_address)
|
||||||
|
|
||||||
|
def create_account(self, label = ""):
|
||||||
|
create_account = {
|
||||||
|
'method': 'create_account',
|
||||||
|
'params' : {
|
||||||
|
'label': label
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(create_account)
|
||||||
|
|
||||||
|
def create_address(self, account_index = 0, label = ""):
|
||||||
|
create_address = {
|
||||||
|
'method': 'create_address',
|
||||||
|
'params' : {
|
||||||
|
'account_index': account_index,
|
||||||
|
'label': label
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(create_address)
|
||||||
|
|
||||||
|
def label_address(self, subaddress_index, label):
|
||||||
|
label_address = {
|
||||||
|
'method': 'label_address',
|
||||||
|
'params' : {
|
||||||
|
'index': { 'major': subaddress_index[0], 'minor': subaddress_index[1]},
|
||||||
|
'label': label
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(label_address)
|
||||||
|
|
||||||
|
def label_account(self, account_index, label):
|
||||||
|
label_account = {
|
||||||
|
'method': 'label_account',
|
||||||
|
'params' : {
|
||||||
|
'account_index': account_index,
|
||||||
|
'label': label
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(label_account)
|
||||||
|
|
||||||
|
def get_address_index(self, address):
|
||||||
|
get_address_index = {
|
||||||
|
'method': 'get_address_index',
|
||||||
|
'params' : {
|
||||||
|
'address': address
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_address_index)
|
||||||
|
|
||||||
|
def query_key(self, key_type):
|
||||||
|
query_key = {
|
||||||
|
'method': 'query_key',
|
||||||
|
'params' : {
|
||||||
|
'key_type': key_type
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(query_key)
|
||||||
|
|
||||||
|
def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = ''):
|
||||||
|
restore_deterministic_wallet = {
|
||||||
|
'method': 'restore_deterministic_wallet',
|
||||||
|
'params' : {
|
||||||
|
'restore_height': restore_height,
|
||||||
|
'filename': filename,
|
||||||
|
'seed': seed,
|
||||||
|
'seed_offset': seed_offset,
|
||||||
|
'password': password,
|
||||||
|
'language': language
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(restore_deterministic_wallet)
|
||||||
|
|
||||||
|
def generate_from_keys(self, restore_height = 0, filename = "", password = "", address = "", spendkey = "", viewkey = ""):
|
||||||
|
generate_from_keys = {
|
||||||
|
'method': 'generate_from_keys',
|
||||||
|
'params' : {
|
||||||
|
'restore_height': restore_height,
|
||||||
|
'filename': filename,
|
||||||
|
'address': address,
|
||||||
|
'spendkey': spendkey,
|
||||||
|
'viewkey': viewkey,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(generate_from_keys)
|
||||||
|
|
||||||
|
def close_wallet(self):
|
||||||
|
close_wallet = {
|
||||||
|
'method': 'close_wallet',
|
||||||
|
'params' : {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(close_wallet)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
refresh = {
|
||||||
|
'method': 'refresh',
|
||||||
|
'params' : {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(refresh)
|
||||||
|
|
||||||
|
def incoming_transfers(self, transfer_type='all', account_index = 0, subaddr_indices = []):
|
||||||
|
incoming_transfers = {
|
||||||
|
'method': 'incoming_transfers',
|
||||||
|
'params' : {
|
||||||
|
'transfer_type': transfer_type,
|
||||||
|
'account_index': account_index,
|
||||||
|
'subaddr_indices': subaddr_indices,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(incoming_transfers)
|
||||||
|
|
||||||
|
def get_transfers(self, in_ = True, out = True, pending = True, failed = True, pool = True, min_height = None, max_height = None, account_index = 0, subaddr_indices = [], all_accounts = False):
|
||||||
|
get_transfers = {
|
||||||
|
'method': 'get_transfers',
|
||||||
|
'params' : {
|
||||||
|
'in': in_,
|
||||||
|
'out': out,
|
||||||
|
'pending': pending,
|
||||||
|
'failed': failed,
|
||||||
|
'pool': pool,
|
||||||
|
'min_height': min_height,
|
||||||
|
'max_height': max_height,
|
||||||
|
'filter_by_height': min_height or max_height,
|
||||||
|
'account_index': account_index,
|
||||||
|
'subaddr_indices': subaddr_indices,
|
||||||
|
'all_accounts': all_accounts,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_transfers)
|
||||||
|
|
||||||
|
def make_integrated_address(self, standard_address = '', payment_id = ''):
|
||||||
|
make_integrated_address = {
|
||||||
|
'method': 'make_integrated_address',
|
||||||
|
'params' : {
|
||||||
|
'standard_address': standard_address,
|
||||||
|
'payment_id': payment_id,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(make_integrated_address)
|
||||||
|
|
||||||
|
def split_integrated_address(self, integrated_address):
|
||||||
|
split_integrated_address = {
|
||||||
|
'method': 'split_integrated_address',
|
||||||
|
'params' : {
|
||||||
|
'integrated_address': integrated_address,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(split_integrated_address)
|
||||||
|
|
||||||
|
def auto_refresh(self, enable, period = 0):
|
||||||
|
auto_refresh = {
|
||||||
|
'method': 'auto_refresh',
|
||||||
|
'params' : {
|
||||||
|
'enable': enable,
|
||||||
|
'period': period
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(auto_refresh)
|
||||||
|
|
||||||
|
def set_daemon(self, address, trusted = False, ssl_support = "autodetect", ssl_private_key_path = "", ssl_certificate_path = "", ssl_allowed_certificates = [], ssl_allowed_fingerprints = [], ssl_allow_any_cert = False):
|
||||||
|
set_daemon = {
|
||||||
|
'method': 'set_daemon',
|
||||||
|
'params' : {
|
||||||
|
'address': address,
|
||||||
|
'trusted': trusted,
|
||||||
|
'ssl_support': ssl_support,
|
||||||
|
'ssl_private_key_path': ssl_private_key_path,
|
||||||
|
'ssl_certificate_path': ssl_certificate_path,
|
||||||
|
'ssl_allowed_certificates': ssl_allowed_certificates,
|
||||||
|
'ssl_allowed_fingerprints': ssl_allowed_fingerprints,
|
||||||
|
'ssl_allow_any_cert': ssl_allow_any_cert,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(set_daemon)
|
||||||
|
|
||||||
|
def is_multisig(self):
|
||||||
|
is_multisig = {
|
||||||
|
'method': 'is_multisig',
|
||||||
|
'params' : {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(is_multisig)
|
||||||
|
|
||||||
|
def prepare_multisig(self):
|
||||||
|
prepare_multisig = {
|
||||||
|
'method': 'prepare_multisig',
|
||||||
|
'params' : {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(prepare_multisig)
|
||||||
|
|
||||||
|
def make_multisig(self, multisig_info, threshold, password = ''):
|
||||||
|
make_multisig = {
|
||||||
|
'method': 'make_multisig',
|
||||||
|
'params' : {
|
||||||
|
'multisig_info': multisig_info,
|
||||||
|
'threshold': threshold,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(make_multisig)
|
||||||
|
|
||||||
|
def exchange_multisig_keys(self, multisig_info, password = ''):
|
||||||
|
exchange_multisig_keys = {
|
||||||
|
'method': 'exchange_multisig_keys',
|
||||||
|
'params' : {
|
||||||
|
'multisig_info': multisig_info,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(exchange_multisig_keys)
|
||||||
|
|
||||||
|
def export_multisig_info(self):
|
||||||
|
export_multisig_info = {
|
||||||
|
'method': 'export_multisig_info',
|
||||||
|
'params' : {
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(export_multisig_info)
|
||||||
|
|
||||||
|
def import_multisig_info(self, info = []):
|
||||||
|
import_multisig_info = {
|
||||||
|
'method': 'import_multisig_info',
|
||||||
|
'params' : {
|
||||||
|
'info': info
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(import_multisig_info)
|
||||||
|
|
||||||
|
def sign_multisig(self, tx_data_hex):
|
||||||
|
sign_multisig = {
|
||||||
|
'method': 'sign_multisig',
|
||||||
|
'params' : {
|
||||||
|
'tx_data_hex': tx_data_hex
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(sign_multisig)
|
||||||
|
|
||||||
|
def submit_multisig(self, tx_data_hex):
|
||||||
|
submit_multisig = {
|
||||||
|
'method': 'submit_multisig',
|
||||||
|
'params' : {
|
||||||
|
'tx_data_hex': tx_data_hex
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(submit_multisig)
|
||||||
|
|
||||||
|
def sign_transfer(self, unsigned_txset, export_raw = False, get_tx_keys = False):
|
||||||
|
sign_transfer = {
|
||||||
|
'method': 'sign_transfer',
|
||||||
|
'params' : {
|
||||||
|
'unsigned_txset': unsigned_txset,
|
||||||
|
'export_raw': export_raw,
|
||||||
|
'get_tx_keys': get_tx_keys,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(sign_transfer)
|
||||||
|
|
||||||
|
def submit_transfer(self, tx_data_hex):
|
||||||
|
submit_transfer = {
|
||||||
|
'method': 'submit_transfer',
|
||||||
|
'params' : {
|
||||||
|
'tx_data_hex': tx_data_hex,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(submit_transfer)
|
||||||
|
|
||||||
|
def get_tx_key(self, txid):
|
||||||
|
get_tx_key = {
|
||||||
|
'method': 'get_tx_key',
|
||||||
|
'params' : {
|
||||||
|
'txid': txid,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_tx_key)
|
||||||
|
|
||||||
|
def check_tx_key(self, txid = '', tx_key = '', address = ''):
|
||||||
|
check_tx_key = {
|
||||||
|
'method': 'check_tx_key',
|
||||||
|
'params' : {
|
||||||
|
'txid': txid,
|
||||||
|
'tx_key': tx_key,
|
||||||
|
'address': address,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(check_tx_key)
|
||||||
|
|
||||||
|
def get_tx_proof(self, txid = '', address = '', message = ''):
|
||||||
|
get_tx_proof = {
|
||||||
|
'method': 'get_tx_proof',
|
||||||
|
'params' : {
|
||||||
|
'txid': txid,
|
||||||
|
'address': address,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_tx_proof)
|
||||||
|
|
||||||
|
def check_tx_proof(self, txid = '', address = '', message = '', signature = ''):
|
||||||
|
check_tx_proof = {
|
||||||
|
'method': 'check_tx_proof',
|
||||||
|
'params' : {
|
||||||
|
'txid': txid,
|
||||||
|
'address': address,
|
||||||
|
'message': message,
|
||||||
|
'signature': signature,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(check_tx_proof)
|
||||||
|
|
||||||
|
def get_reserve_proof(self, all_ = True, account_index = 0, amount = 0, message = ''):
|
||||||
|
get_reserve_proof = {
|
||||||
|
'method': 'get_reserve_proof',
|
||||||
|
'params' : {
|
||||||
|
'all': all_,
|
||||||
|
'account_index': account_index,
|
||||||
|
'amount': amount,
|
||||||
|
'message': message,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_reserve_proof)
|
||||||
|
|
||||||
|
def check_reserve_proof(self, address = '', message = '', signature = ''):
|
||||||
|
check_reserve_proof = {
|
||||||
|
'method': 'check_reserve_proof',
|
||||||
|
'params' : {
|
||||||
|
'address': address,
|
||||||
|
'message': message,
|
||||||
|
'signature': signature,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(check_reserve_proof)
|
||||||
|
|
||||||
|
def sign(self, data):
|
||||||
|
sign = {
|
||||||
|
'method': 'sign',
|
||||||
|
'params' : {
|
||||||
|
'data': data,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(sign)
|
||||||
|
|
||||||
|
def verify(self, data, address, signature):
|
||||||
|
verify = {
|
||||||
|
'method': 'verify',
|
||||||
|
'params' : {
|
||||||
|
'data': data,
|
||||||
|
'address': address,
|
||||||
|
'signature': signature,
|
||||||
|
},
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(verify)
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
get_version = {
|
||||||
|
'method': 'get_version',
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': '0'
|
||||||
|
}
|
||||||
|
return self.rpc.send_json_rpc_request(get_version)
|
Loading…
Reference in a new issue