Skip to content

Commit

Permalink
Add wallet command to create a send coins request for cold wallet input
Browse files Browse the repository at this point in the history
  • Loading branch information
OBorce committed Feb 4, 2024
1 parent e00561b commit a0e3ef7
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 83 deletions.
11 changes: 8 additions & 3 deletions test/functional/test_framework/wallet_cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,10 @@ async def set_lookahead_size(self, size: int, force_reduce: bool) -> str:
i_know_what_i_am_doing = "i-know-what-i-am-doing" if force_reduce else ""
return await self._write_command(f"wallet-set-lookahead-size {size} {i_know_what_i_am_doing}\n")

async def new_public_key(self) -> bytes:
addr = await self.new_address()
public_key = await self._write_command(f"address-reveal-public-key-as-hex {addr}\n")
async def new_public_key(self, address: Optional[str] = None) -> bytes:
if address is None:
address = await self.new_address()
public_key = await self._write_command(f"address-reveal-public-key-as-hex {address}\n")

self.log.info(f'pub key output: {public_key}')
# remove the pub key enum value, the first one byte
Expand All @@ -216,6 +217,10 @@ async def get_transaction(self, tx_id: str) -> str:
async def get_raw_signed_transaction(self, tx_id: str) -> str:
return await self._write_command(f"transaction-get-signed-raw {tx_id}\n")

async def send_from_cold_address(self, address: str, amount: int, selected_utxo: UtxoOutpoint, change_address: Optional[str] = None) -> str:
change_address_str = '' if change_address is None else f"--change {change_address}"
return await self._write_command(f"transaction-send-from-cold-input {address} {amount} {str(selected_utxo)} {change_address_str}\n")

async def send_to_address(self, address: str, amount: int, selected_utxos: List[UtxoOutpoint] = []) -> str:
return await self._write_command(f"address-send {address} {amount} {' '.join(map(str, selected_utxos))}\n")

Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class UnicodeOnWindowsError(ValueError):
'feature_db_reinit.py',
'feature_lmdb_backend_test.py',
'wallet_conflict.py',
'wallet_cold_wallet_send.py',
'wallet_tx_compose.py',
'wallet_data_deposit.py',
'wallet_submit_tx.py',
Expand Down
164 changes: 164 additions & 0 deletions test/functional/wallet_cold_wallet_send.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
# Copyright (c) 2023 RBB S.r.l
# Copyright (c) 2017-2021 The Bitcoin Core developers
# [email protected]
# SPDX-License-Identifier: MIT
# Licensed under the MIT License;
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Wallet cold wallet send request test
Check that:
* We can create a new cold wallet,
* issue a new address
* send some coins to that address
* create a new hot wallet
* from the hot wallet create a send request using the cold wallet's utxo
* sign the new tx with the cold wallet
* send it with the hot wallet
"""

from test_framework.authproxy import JSONRPCException
from test_framework.mintlayer import (
block_input_data_obj,
ATOMS_PER_COIN,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.mintlayer import (make_tx, reward_input)
from test_framework.util import assert_equal, assert_in
from test_framework.mintlayer import block_input_data_obj
from test_framework.wallet_cli_controller import UtxoOutpoint, WalletCliController

import asyncio
import sys
import time

class WalletColdSend(BitcoinTestFramework):

def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"--blockprod-min-peers-to-produce-blocks=0",
]]

def setup_network(self):
self.setup_nodes()
self.sync_all(self.nodes[0:1])

def generate_block(self):
node = self.nodes[0]

block_input_data = { "PoW": { "reward_destination": "AnyoneCanSpend" } }
block_input_data = block_input_data_obj.encode(block_input_data).to_hex()[2:]

# create a new block, taking transactions from mempool
block = node.blockprod_generate_block(block_input_data, [], [], "FillSpaceFromMempool")
node.chainstate_submit_block(block)
block_id = node.chainstate_best_block_id()

# Wait for mempool to sync
self.wait_until(lambda: node.mempool_local_best_block_id() == block_id, timeout = 5)

return block_id

def hex_to_dec_array(self, hex_string):
return [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)]

def previous_block_id(self):
previous_block_id = self.nodes[0].chainstate_best_block_id()
return self.hex_to_dec_array(previous_block_id)

def run_test(self):
if 'win32' in sys.platform:
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
asyncio.run(self.async_test())

async def async_test(self):
node = self.nodes[0]
cold_wallet_pk = ""

async with WalletCliController(node, self.config, self.log, chain_config_args=["--chain-pos-netupgrades", "true", "--cold-wallet"]) as wallet:
# new cold wallet
await wallet.create_wallet("cold_wallet")

cold_wallet_address = await wallet.new_address()
cold_wallet_pk = await wallet.new_public_key(cold_wallet_address)
assert_equal(len(cold_wallet_pk), 33)

tx_req = ""

async with WalletCliController(node, self.config, self.log, chain_config_args=["--chain-pos-netupgrades", "true"]) as wallet:
# new hot wallet
await wallet.create_wallet("hot_wallet")

# check it is on genesis
best_block_height = await wallet.get_best_block_height()
self.log.info(f"best block height = {best_block_height}")
assert_equal(best_block_height, '0')

# Get chain tip
tip_id = node.chainstate_best_block_id()
self.log.debug(f'Tip: {tip_id}')

# Submit a valid transaction
output = {
'Transfer': [ { 'Coin': 50_000 * ATOMS_PER_COIN }, { 'PublicKey': {'key': {'Secp256k1Schnorr' : {'pubkey_data': cold_wallet_pk}}} } ],
}
encoded_tx, tx_id = make_tx([reward_input(tip_id)], [output], 0)
self.log.debug(f"Encoded transaction {tx_id}: {encoded_tx}")

node.mempool_submit_transaction(encoded_tx, {})
assert node.mempool_contains_tx(tx_id)

self.generate_block()

balance = await wallet.get_balance()
assert_in("Coins amount: 0", balance)

hot_wallet_address = await wallet.new_address()

output = await wallet.send_from_cold_address(hot_wallet_address, 1, UtxoOutpoint(tx_id, 0))
assert_in("Send transaction created", output)
send_req = output.split("\n")[2]

# try to sign decommission request from hot wallet
assert_in("Wallet error: Wallet error: Input cannot be signed",
await wallet.sign_raw_transaction(send_req))

signed_tx = ""

async with WalletCliController(node, self.config, self.log, chain_config_args=["--chain-pos-netupgrades", "true", "--cold-wallet"]) as wallet:
# open cold wallet
await wallet.open_wallet("cold_wallet")

# sign decommission request
signed_tx_output = await wallet.sign_raw_transaction(send_req)
signed_tx = signed_tx_output.split('\n')[2]

async with WalletCliController(node, self.config, self.log, chain_config_args=["--chain-pos-netupgrades", "true"]) as wallet:
# open hot wallet
await wallet.open_wallet("hot_wallet")

assert_in("The transaction was submitted successfully", await wallet.submit_transaction(signed_tx))

transactions = node.mempool_transactions()
assert_in(signed_tx, transactions)

self.generate_block()
assert_in("Success", await wallet.sync())

balance = await wallet.get_balance()
assert_in("Coins amount: 1", balance)

if __name__ == '__main__':
WalletColdSend().main()

Loading

0 comments on commit a0e3ef7

Please sign in to comment.