Cleaner send_transaction flow and more wallet rpc testing

This commit is contained in:
Mariano Sorgente 2020-09-11 17:26:25 +09:00 committed by Gene Hoffman
parent 9ed6949099
commit b791bf1da8
18 changed files with 243 additions and 242 deletions

View File

@ -25,12 +25,14 @@ proofs of space during testing. The next time tests are run, this won't be neces
```bash
. ./activate
pip install -r requirements-dev.txt
black src tests && flake8 src --exclude src/wallet/electron/node_modules && mypy src tests
py.test tests -s -v
black src tests && flake8 src tests && mypy src tests
py.test tests -s -v --durations 0
```
Black is used as an automatic style formatter to make things easier, and flake8 helps ensure consistent style.
Mypy is very useful for ensuring objects are of the correct type, so try to always add the type of the return value, and the type of local variables.
If you want verbose logging for tests, edit the tests/pytest.ini file.
## Configure VS code
1. Install Python extension
2. Set the environment to ./venv/bin/python

View File

@ -1,5 +1,5 @@
import {
get_puzzle_hash,
get_address,
format_message,
incomingMessage,
get_balance_for_wallet,
@ -201,7 +201,7 @@ export const handle_message = (store, payload) => {
store.dispatch(get_balance_for_wallet(wallet.id));
store.dispatch(get_transactions(wallet.id));
if (wallet.type === COLOURED_COIN || wallet.type === STANDARD_WALLET) {
store.dispatch(get_puzzle_hash(wallet.id));
store.dispatch(get_address(wallet.id));
}
if (wallet.type === COLOURED_COIN) {
store.dispatch(get_colour_name(wallet.id));

View File

@ -11,15 +11,16 @@ export const Wallet = (id, name, type, data) => ({
balance_frozen: 0,
balance_change: 0,
transactions: [],
puzzle_hash: "",
address: "",
colour: "",
sending_transaction: false,
send_transaction_result: ""
});
export const Transaction = (
confirmed_at_index,
created_at_time,
to_puzzle_hash,
to_address,
amount,
fee_amount,
incoming,
@ -32,7 +33,7 @@ export const Transaction = (
) => ({
confirmed_at_index: confirmed_at_index,
created_at_time: created_at_time,
to_puzzle_hash: to_puzzle_hash,
to_address: to_address,
amount: amount,
fee_amount: fee_amount,
incoming: incoming,
@ -58,7 +59,6 @@ const initial_state = {
connection_count: 0,
syncing: false
},
sending_transaction: false,
show_create_backup: false
};
@ -87,20 +87,24 @@ export const incomingReducer = (state = { ...initial_state }, action) => {
};
case "CLEAR_SEND":
id = action.message.data.wallet_id;
wallet = state.wallets[parseInt(id)];
wallet.sending_transaction = false;
wallet.send_transaction_result = null;
return {
...state,
sending_transaction: false,
send_transaction_result: null
};
case "OUTGOING_MESSAGE":
if (
action.message.command === "send_transaction" ||
action.message.command === "cc_spend"
) {
id = action.message.data.wallet_id;
wallet = state.wallets[parseInt(id)];
wallet.sending_transaction = false;
wallet.send_transaction_result = null;
return {
...state,
sending_transaction: true,
send_transaction_result: null
};
}
return state;
@ -190,15 +194,15 @@ export const incomingReducer = (state = { ...initial_state }, action) => {
wallet.transactions = transactions.reverse();
return { ...state };
}
} else if (command === "get_next_puzzle_hash") {
} else if (command === "get_next_address") {
id = data.wallet_id;
var puzzle_hash = data.puzzle_hash;
var address = data.address;
wallets = state.wallets;
wallet = wallets[parseInt(id)];
if (!wallet) {
return state;
}
wallet.puzzle_hash = puzzle_hash;
wallet.address = address;
return { ...state };
} else if (command === "get_connections") {
if (data.success || data.connections) {
@ -241,12 +245,12 @@ export const incomingReducer = (state = { ...initial_state }, action) => {
wallet.name = name;
return { ...state };
}
if (command === "send_transaction" || command === "cc_spend") {
state["sending_transaction"] = false;
if (command === "tx_update") {
const id = data.id;
wallets = state.wallets;
wallet = wallets[parseInt(id)];
wallet.send_transaction_result = message.data;
wallet.sending_transaction = false;
wallet.send_transaction_result = message.data.additional_data;
return { ...state };
}
return state;

View File

@ -102,14 +102,18 @@ export const get_balance_for_wallet = id => {
return action;
};
export const send_transaction = (wallet_id, amount, fee, puzzle_hash) => {
export const send_transaction = (wallet_id, amount, fee, address) => {
var action = walletMessage();
action.message.command = "send_transaction";
action.message.data = {
wallet_id: wallet_id,
amount: amount,
fee: fee,
<<<<<<< HEAD
puzzle_hash: puzzle_hash
=======
address: address,
>>>>>>> c125573c... Cleaner send_transaction flow and more wallet rpc testing
};
return action;
};
@ -132,8 +136,29 @@ export const add_key = (mnemonic, type, file_path) => {
return action;
};
<<<<<<< HEAD
export const add_new_key_action = mnemonic => {
return dispatch => {
=======
export const send_transaction_and_wait = (wallet_id, amount, fee, address) => {
return (dispatch) => {
try {
response = await async_api(dispatch, send_transaction(wallet_id, amount, fee, address), False);
if (!response.data.success) {
// Do something bad
return;
}
// Do something good
} catch (err) {
// Do something bad
return;
}
}
}
export const add_new_key_action = (mnemonic) => {
return (dispatch) => {
>>>>>>> c125573c... Cleaner send_transaction flow and more wallet rpc testing
return async_api(
dispatch,
add_key(mnemonic, "new_wallet", null),
@ -388,17 +413,25 @@ export const get_transactions = wallet_id => {
return action;
};
<<<<<<< HEAD
export const get_puzzle_hash = wallet_id => {
=======
export const get_address = (wallet_id) => {
>>>>>>> c125573c... Cleaner send_transaction flow and more wallet rpc testing
var action = walletMessage();
action.message.command = "get_next_puzzle_hash";
action.message.command = "get_next_address";
action.message.data = { wallet_id: wallet_id };
return action;
};
<<<<<<< HEAD
export const farm_block = puzzle_hash => {
=======
export const farm_block = (address) => {
>>>>>>> c125573c... Cleaner send_transaction flow and more wallet rpc testing
var action = walletMessage();
action.message.command = "farm_block";
action.message.data = { puzzle_hash: puzzle_hash };
action.message.data = { address: address };
return action;
};
@ -536,12 +569,12 @@ export const rename_cc_wallet = (wallet_id, name) => {
return action;
};
export const cc_spend = (wallet_id, puzzle_hash, amount, fee) => {
export const cc_spend = (wallet_id, address, amount, fee) => {
var action = walletMessage();
action.message.command = "cc_spend";
action.message.data = {
wallet_id: wallet_id,
innerpuzhash: puzzle_hash,
inner_address: address,
amount: amount,
fee: fee
};

View File

@ -16,7 +16,7 @@ import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import {
get_puzzle_hash,
get_address,
cc_spend,
farm_block,
rename_cc_wallet
@ -29,6 +29,7 @@ import {
import { unix_to_short_date } from "../util/utils";
import Accordion from "../components/Accordion";
import { openDialog } from "../modules/dialogReducer";
import { get_transaction_result } from "../util/transaction_result";
const config = require("../config");
const drawerWidth = 240;
@ -417,7 +418,7 @@ const SendCard = props => {
const cc_unit = get_cc_unit(name);
const sending_transaction = useSelector(
state => state.wallet_state.sending_transaction
state => state.wallet_state.wallets[id].sending_transaction
);
const send_transaction_result = useSelector(
@ -426,21 +427,9 @@ const SendCard = props => {
const colour = useSelector(state => state.wallet_state.wallets[id].colour);
let result_message = "";
let result_class = classes.resultSuccess;
if (send_transaction_result) {
if (send_transaction_result.status === "SUCCESS") {
result_message =
"Transaction has successfully been sent to a full node and included in the mempool.";
} else if (send_transaction_result.status === "PENDING") {
result_message =
"Transaction has sent to a full node and is pending inclusion into the mempool. " +
send_transaction_result.reason;
} else {
result_message = "Transaction failed. " + send_transaction_result.reason;
result_class = classes.resultFailure;
}
}
result = get_transaction_result(send_transaction_result);
let result_message = result.message;
let result_class = result.success ? classes.resultSuccess : classes.resultFailure;
function farm() {
var address = address_input.value;
@ -453,7 +442,7 @@ const SendCard = props => {
if (sending_transaction) {
return;
}
let puzzle_hash = address_input.value.trim();
let address = address_input.value.trim();
if (
amount_input.value === "" ||
Number(amount_input.value) === 0 ||
@ -472,8 +461,8 @@ const SendCard = props => {
const fee = colouredcoin_to_mojo(fee_input.value);
if (
puzzle_hash.includes("chia_addr") ||
puzzle_hash.includes("colour_desc")
address.includes("chia_addr") ||
address.includes("colour_desc")
) {
dispatch(
openDialog(
@ -482,9 +471,9 @@ const SendCard = props => {
);
return;
}
if (puzzle_hash.substring(0, 14) === "colour_addr://") {
const colour_id = puzzle_hash.substring(14, 78);
puzzle_hash = puzzle_hash.substring(79);
if (address.substring(0, 14) === "colour_addr://") {
const colour_id = address.substring(14, 78);
address = address.substring(79);
if (colour_id !== colour) {
dispatch(
openDialog(
@ -495,14 +484,14 @@ const SendCard = props => {
}
}
if (puzzle_hash.startsWith("0x") || puzzle_hash.startsWith("0X")) {
puzzle_hash = puzzle_hash.substring(2);
if (address.startsWith("0x") || address.startsWith("0X")) {
address = address.substring(2);
}
const amount_value = parseFloat(Number(amount));
const fee_value = parseFloat(Number(fee));
dispatch(cc_spend(id, puzzle_hash, amount_value, fee_value));
dispatch(cc_spend(id, address, amount_value, fee_value));
address_input.value = "";
amount_input.value = "";
}
@ -669,7 +658,7 @@ const TransactionTable = props => {
{transactions.map(tx => (
<TableRow
className={classes.row}
key={tx.to_puzzle_hash + tx.created_at_time + tx.amount}
key={tx.to_address + tx.created_at_time + tx.amount}
>
<TableCell className={classes.cell_short}>
{incoming_string(tx.incoming)}
@ -678,7 +667,7 @@ const TransactionTable = props => {
style={{ maxWidth: "150px" }}
className={classes.cell_short}
>
{tx.to_puzzle_hash}
{tx.to_address}
</TableCell>
<TableCell className={classes.cell_short}>
{unix_to_short_date(tx.created_at_time)}
@ -702,18 +691,18 @@ const TransactionTable = props => {
const AddressCard = props => {
var id = props.wallet_id;
const puzzle_hash = useSelector(
state => state.wallet_state.wallets[id].puzzle_hash
const address = useSelector(
state => state.wallet_state.wallets[id].address
);
const classes = useStyles();
const dispatch = useDispatch();
function newAddress() {
dispatch(get_puzzle_hash(id));
dispatch(get_address(id));
}
function copy() {
navigator.clipboard.writeText(puzzle_hash);
navigator.clipboard.writeText(address);
}
return (
@ -734,7 +723,7 @@ const AddressCard = props => {
disabled
fullWidth
label="Address"
value={puzzle_hash}
value={address}
variant="outlined"
/>
</Box>

View File

@ -643,34 +643,22 @@ const SendCard = props => {
const dispatch = useDispatch();
const sending_transaction = useSelector(
state => state.wallet_state.sending_transaction
state => state.wallet_state.wallets[id].sending_transaction
);
const send_transaction_result = useSelector(
state => state.wallet_state.wallets[id].send_transaction_result
);
let result_message = "";
let result_class = classes.resultSuccess;
if (send_transaction_result) {
if (send_transaction_result.status === "SUCCESS") {
result_message =
"Transaction has successfully been sent to a full node and included in the mempool.";
} else if (send_transaction_result.status === "PENDING") {
result_message =
"Transaction has sent to a full node and is pending inclusion into the mempool. " +
send_transaction_result.reason;
} else {
result_message = "Transaction failed. " + send_transaction_result.reason;
result_class = classes.resultFailure;
}
}
result = get_transaction_result(send_transaction_result);
let result_message = result.message;
let result_class = result.success ? classes.resultSuccess : classes.resultFailure;
function send() {
if (sending_transaction) {
return;
}
let puzzle_hash = address_input.value.trim();
let address = address_input.value.trim();
if (
amount_input.value === "" ||
Number(amount_input.value) === 0 ||
@ -687,14 +675,14 @@ const SendCard = props => {
const amount = chia_to_mojo(amount_input.value);
const fee = chia_to_mojo(fee_input.value);
if (puzzle_hash.startsWith("0x") || puzzle_hash.startsWith("0X")) {
puzzle_hash = puzzle_hash.substring(2);
if (address.startsWith("0x") || address.startsWith("0X")) {
address = address.substring(2);
}
const amount_value = parseFloat(Number(amount));
const fee_value = parseFloat(Number(fee));
dispatch(send_transaction(id, amount_value, fee_value, puzzle_hash));
dispatch(send_transaction(id, amount_value, fee_value, address));
address_input.value = "";
amount_input.value = "";
fee_input.value = "";
@ -898,7 +886,7 @@ const TransactionTable = props => {
{transactions.map(tx => (
<TableRow
className={classes.row}
key={tx.to_puzzle_hash + tx.created_at_time + tx.amount}
key={tx.to_address + tx.created_at_time + tx.amount}
>
<TableCell className={classes.cell_short}>
{incoming_string(tx.incoming)}
@ -907,7 +895,7 @@ const TransactionTable = props => {
style={{ maxWidth: "150px" }}
className={classes.cell_short}
>
{tx.to_puzzle_hash}
{tx.to_address}
</TableCell>
<TableCell className={classes.cell_short}>
{unix_to_short_date(tx.created_at_time)}

View File

@ -15,7 +15,7 @@ import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import {
get_puzzle_hash,
get_address,
send_transaction,
farm_block
} from "../modules/message";
@ -326,27 +326,16 @@ const SendCard = props => {
const dispatch = useDispatch();
const sending_transaction = useSelector(
state => state.wallet_state.sending_transaction
state => state.wallet_state.wallets[id].sending_transaction
);
const send_transaction_result = useSelector(
state => state.wallet_state.wallets[id].send_transaction_result
);
let result_message = "";
let result_class = classes.resultSuccess;
if (send_transaction_result) {
if (send_transaction_result.status === "SUCCESS") {
result_message =
"Transaction has successfully been sent to a full node and included in the mempool.";
} else if (send_transaction_result.status === "PENDING") {
result_message =
"Transaction has sent to a full node and is pending inclusion into the mempool. " +
send_transaction_result.reason;
} else {
result_message = "Transaction failed. " + send_transaction_result.reason;
result_class = classes.resultFailure;
}
}
result = get_transaction_result(send_transaction_result);
let result_message = result.message;
let result_class = result.success ? classes.resultSuccess : classes.resultFailure;
function farm() {
var address = address_input.value;
@ -359,7 +348,7 @@ const SendCard = props => {
if (sending_transaction) {
return;
}
let puzzle_hash = address_input.value.trim();
let address = address_input.value.trim();
if (
amount_input.value === "" ||
Number(amount_input.value) === 0 ||
@ -376,24 +365,24 @@ const SendCard = props => {
const amount = chia_to_mojo(amount_input.value);
const fee = chia_to_mojo(fee_input.value);
if (puzzle_hash.includes("colour")) {
if (address.includes("colour")) {
dispatch(
openDialog(
"Error: Cannot send chia to coloured address. Please enter a chia address."
)
);
return;
} else if (puzzle_hash.substring(0, 12) === "chia_addr://") {
puzzle_hash = puzzle_hash.substring(12);
} else if (address.substring(0, 12) === "chia_addr://") {
address = address.substring(12);
}
if (puzzle_hash.startsWith("0x") || puzzle_hash.startsWith("0X")) {
puzzle_hash = puzzle_hash.substring(2);
if (address.startsWith("0x") || address.startsWith("0X")) {
address = address.substring(2);
}
const amount_value = parseFloat(Number(amount));
const fee_value = parseFloat(Number(fee));
dispatch(send_transaction(id, amount_value, fee_value, puzzle_hash));
dispatch(send_transaction(id, amount_value, fee_value, address));
address_input.value = "";
amount_input.value = "";
fee_input.value = "";
@ -565,7 +554,7 @@ const TransactionTable = props => {
<TableRow
className={classes.row}
key={
tx.to_puzzle_hash +
tx.to_address +
tx.created_at_time +
tx.amount +
(tx.removals.length > 0 ? tx.removals[0].parent_coin_info : "")
@ -578,7 +567,7 @@ const TransactionTable = props => {
style={{ maxWidth: "150px" }}
className={classes.cell_short}
>
{tx.to_puzzle_hash}
{tx.to_address}
</TableCell>
<TableCell className={classes.cell_short}>
{unix_to_short_date(tx.created_at_time)}
@ -602,18 +591,18 @@ const TransactionTable = props => {
const AddressCard = props => {
var id = props.wallet_id;
const puzzle_hash = useSelector(
state => state.wallet_state.wallets[id].puzzle_hash
const address = useSelector(
state => state.wallet_state.wallets[id].address
);
const classes = useStyles();
const dispatch = useDispatch();
function newAddress() {
dispatch(get_puzzle_hash(id));
dispatch(get_address(id));
}
function copy() {
navigator.clipboard.writeText(puzzle_hash);
navigator.clipboard.writeText(address);
}
return (
@ -634,7 +623,7 @@ const AddressCard = props => {
disabled
fullWidth
label="Address"
value={puzzle_hash}
value={address}
variant="outlined"
/>
</Box>

View File

@ -0,0 +1,37 @@
const mempool_inclusion_status = {
"SUCCESS": 1, // Transaction added to mempool
"PENDING": 2, // Transaction not yet added to mempool
"FAILED": 3, // Transaction was invalid and dropped
}
function get_transaction_result(transaction) {
let success = true;
let message = "The transaction result was received";
for (let full_node_response of transaction.transaction.sent_to) {
console.log("full node response", full_node_response);
}
// if (send_transaction_result) {
// if (send_transaction_result.status === "SUCCESS") {
// result_message =
// "Transaction has successfully been sent to a full node and included in the mempool.";
// } else if (send_transaction_result.status === "PENDING") {
// result_message =
// "Transaction has sent to a full node and is pending inclusion into the mempool. " +
// send_transaction_result.reason;
// } else {
// result_message = "Transaction failed. " + send_transaction_result.reason;
// result_class = classes.resultFailure;
// }
// }
return {
message,
success
}
}
module.exports = {
mempool_inclusion_status,
get_transaction_result,
}

View File

@ -23,6 +23,7 @@ import yaml
from src.ssl.create_ssl import generate_selfsigned_cert
from src.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_pool_sk
from src.util.chech32 import encode_puzzle_hash
def make_parser(parser: ArgumentParser):
@ -54,50 +55,50 @@ def check_keys(new_root):
config: Dict = load_config(new_root, "config.yaml")
pool_child_pubkeys = [master_sk_to_pool_sk(sk).get_g1() for sk, _ in all_sks]
all_targets = []
stop_searching_for_farmer = "xch_target_puzzle_hash" not in config["farmer"]
stop_searching_for_pool = "xch_target_puzzle_hash" not in config["pool"]
stop_searching_for_farmer = "xch_target_address" not in config["farmer"]
stop_searching_for_pool = "xch_target_address" not in config["pool"]
for i in range(500):
if stop_searching_for_farmer and stop_searching_for_pool and i > 0:
break
for sk, _ in all_sks:
all_targets.append(
create_puzzlehash_for_pk(
encode_puzzle_hash(create_puzzlehash_for_pk(
master_sk_to_wallet_sk(sk, uint32(i)).get_g1()
).hex()
))
)
if all_targets[-1] == config["farmer"].get("xch_target_puzzle_hash"):
if all_targets[-1] == config["farmer"].get("xch_target_address"):
stop_searching_for_farmer = True
if all_targets[-1] == config["pool"].get("xch_target_puzzle_hash"):
if all_targets[-1] == config["pool"].get("xch_target_address"):
stop_searching_for_pool = True
# Set the destinations
if "xch_target_puzzle_hash" not in config["farmer"]:
if "xch_target_address" not in config["farmer"]:
print(
f"Setting the xch destination address for coinbase fees reward to {all_targets[0]}"
)
config["farmer"]["xch_target_puzzle_hash"] = all_targets[0]
elif config["farmer"]["xch_target_puzzle_hash"] not in all_targets:
config["farmer"]["xch_target_address"] = all_targets[0]
elif config["farmer"]["xch_target_address"] not in all_targets:
print(
f"WARNING: farmer using a puzzle hash which we don't have the private"
f" keys for. Overriding "
f"{config['farmer']['xch_target_puzzle_hash']} with {all_targets[0]}"
f"{config['farmer']['xch_target_address']} with {all_targets[0]}"
)
config["farmer"]["xch_target_puzzle_hash"] = all_targets[0]
config["farmer"]["xch_target_address"] = all_targets[0]
if "pool" not in config:
config["pool"] = {}
if "xch_target_puzzle_hash" not in config["pool"]:
if "xch_target_address" not in config["pool"]:
print(
f"Setting the xch destination address for coinbase reward to {all_targets[0]}"
)
config["pool"]["xch_target_puzzle_hash"] = all_targets[0]
elif config["pool"]["xch_target_puzzle_hash"] not in all_targets:
config["pool"]["xch_target_address"] = all_targets[0]
elif config["pool"]["xch_target_address"] not in all_targets:
print(
f"WARNING: pool using a puzzle hash which we don't have the private"
f" keys for. Overriding "
f"{config['pool']['xch_target_puzzle_hash']} with {all_targets[0]}"
f"{config['pool']['xch_target_address']} with {all_targets[0]}"
)
config["pool"]["xch_target_puzzle_hash"] = all_targets[0]
config["pool"]["xch_target_address"] = all_targets[0]
# Set the pool pks in the farmer
pool_pubkeys_hex = set(bytes(pk).hex() for pk in pool_child_pubkeys)

View File

@ -15,6 +15,7 @@ from src.util.config import load_config
from src.util.default_root import DEFAULT_ROOT_PATH
from src.wallet.util.wallet_types import WalletType
from src.cmds.units import units
from src.util.chech32 import encode_puzzle_hash
def make_parser(parser):
@ -304,9 +305,9 @@ async def show_async(args, parser):
f"Tx Filter Hash {b'block.transactions_filter'.hex()}\n"
f"Tx Generator Hash {block.transactions_generator}\n"
f"Coinbase Amount {block.get_coinbase().amount/1000000000000}\n"
f"Coinbase Puzzle Hash 0x{block.get_coinbase().puzzle_hash}\n"
f"Coinbase Address {encode_puzzle_hash(block.get_coinbase().puzzle_hash)}\n"
f"Fees Amount {block.get_fees_coin().amount/1000000000000}\n"
f"Fees Puzzle Hash 0x{block.get_fees_coin().puzzle_hash}\n"
f"Fees Address {encode_puzzle_hash(block.get_fees_coin().puzzle_hash)}\n"
f"Aggregated Signature {aggregated_signature}"
)
else:

View File

@ -16,6 +16,7 @@ from src.types.pool_target import PoolTarget
from src.util.api_decorators import api_request
from src.util.ints import uint32, uint64, uint128, uint8
from src.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk
from src.util.chech32 import decode_puzzle_hash
log = logging.getLogger(__name__)
@ -57,14 +58,14 @@ class Farmer:
raise RuntimeError(error_str)
# This is the farmer configuration
self.wallet_target = bytes.fromhex(self.config["xch_target_puzzle_hash"])
self.wallet_target = decode_puzzle_hash(bytes.fromhex(self.config["xch_target_address"]))
self.pool_public_keys = [
G1Element.from_bytes(bytes.fromhex(pk))
for pk in self.config["pool_public_keys"]
]
# This is the pool configuration, which should be moved out to the pool once it exists
self.pool_target = bytes.fromhex(pool_config["xch_target_puzzle_hash"])
self.pool_target = decode_puzzle_hash(bytes.fromhex(pool_config["xch_target_address"]))
self.pool_sks_map: Dict = {}
for key in self._get_private_keys():
self.pool_sks_map[bytes(key.get_g1())] = key

View File

@ -560,7 +560,7 @@ class FullNode:
if self.mempool_manager.seen(transaction.transaction_id):
return
elif self.mempool_manager.is_fee_enough(transaction.fees, transaction.cost):
if self.mempool_manager.is_fee_enough(transaction.fees, transaction.cost):
requestTX = full_node_protocol.RequestTransaction(
transaction.transaction_id
)
@ -613,10 +613,12 @@ class FullNode:
# Ignore if we have already added this transaction
if self.mempool_manager.get_spendbundle(tx.transaction.name()) is not None:
return
self.log.warning(f"Adding transaction to mempool: {transaction.transaction_id}")
cost, status, error = await self.mempool_manager.add_spendbundle(
tx.transaction
)
if status == MempoolInclusionStatus.SUCCESS:
self.log.warning(f"Added transaction to mempool: {transaction.transaction_id}")
fees = tx.transaction.fees()
assert fees >= 0
assert cost is not None
@ -1823,6 +1825,7 @@ class FullNode:
tx.transaction
)
if status == MempoolInclusionStatus.SUCCESS:
self.log.info(f"Added transaction to mempool: {tx.transaction.name()}")
# Only broadcast successful transactions, not pending ones. Otherwise it's a DOS
# vector.
fees = tx.transaction.fees()

View File

@ -45,7 +45,12 @@ class WalletRpcApi:
return {
"/get_wallet_balance": self.get_wallet_balance,
"/send_transaction": self.send_transaction,
<<<<<<< HEAD
"/get_next_puzzle_hash": self.get_next_puzzle_hash,
=======
"/get_next_address": self.get_next_address,
"/get_transaction": self.get_transaction,
>>>>>>> c125573c... Cleaner send_transaction flow and more wallet rpc testing
"/get_transactions": self.get_transactions,
"/farm_block": self.farm_block,
"/get_sync_status": self.get_sync_status,
@ -155,18 +160,18 @@ class WalletRpcApi:
if len(args) < 2:
return []
change = args[0]
wallet_id = args[1]
data = {
"state": change,
"state": args[0],
}
if wallet_id is not None:
data["wallet_id"] = wallet_id
if args[1] is not None:
data["wallet_id"] = args[1]
if args[2] is not None:
data["additional_data"] = args[2]
return [create_payload("state_changed", data, "chia_wallet", "wallet_ui")]
async def get_next_puzzle_hash(self, request: Dict) -> Dict:
async def get_next_address(self, request: Dict) -> Dict:
"""
Returns a new puzzlehash
Returns a new address
"""
if self.service is None:
return {"success": False}
@ -178,10 +183,10 @@ class WalletRpcApi:
if wallet.wallet_info.type == WalletType.STANDARD_WALLET.value:
raw_puzzle_hash = await wallet.get_new_puzzlehash()
puzzle_hash = encode_puzzle_hash(raw_puzzle_hash)
address = encode_puzzle_hash(raw_puzzle_hash)
elif wallet.wallet_info.type == WalletType.COLOURED_COIN.value:
raw_puzzle_hash = await wallet.get_new_inner_hash()
puzzle_hash = encode_puzzle_hash(raw_puzzle_hash)
address = encode_puzzle_hash(raw_puzzle_hash)
else:
return {
"success": False,
@ -191,13 +196,12 @@ class WalletRpcApi:
response = {
"success": True,
"wallet_id": wallet_id,
"puzzle_hash": puzzle_hash,
"address": address,
}
return response
async def send_transaction(self, request):
log.debug("rpc_api request: " + str(request))
wallet_id = int(request["wallet_id"])
wallet = self.service.wallet_state_manager.wallets[wallet_id]
try:
@ -270,7 +274,7 @@ class WalletRpcApi:
for tx in transactions:
formatted = tx.to_json_dict()
formatted["to_puzzle_hash"] = encode_puzzle_hash(tx.to_puzzle_hash)
formatted["to_address"] = encode_puzzle_hash(tx.to_address)
formatted_transactions.append(formatted)
response = {
@ -462,7 +466,7 @@ class WalletRpcApi:
async def cc_spend(self, request):
wallet_id = int(request["wallet_id"])
wallet: CCWallet = self.service.wallet_state_manager.wallets[wallet_id]
encoded_puzzle_hash = request["innerpuzhash"]
encoded_puzzle_hash = request["inner_address"]
puzzle_hash = decode_puzzle_hash(encoded_puzzle_hash)
try:
@ -475,7 +479,7 @@ class WalletRpcApi:
if tx is None:
data = {
"status": "FAILED",
"success": False,
"reason": "Failed to generate signed transaction",
"id": wallet_id,
}
@ -484,47 +488,18 @@ class WalletRpcApi:
await wallet.wallet_state_manager.add_pending_transaction(tx)
except Exception as e:
data = {
"status": "FAILED",
"success": False,
"reason": f"Failed to push transaction {e}",
}
return data
sent = False
start = time.time()
while time.time() - start < TIMEOUT:
sent_to: List[
Tuple[str, MempoolInclusionStatus, Optional[str]]
] = await self.service.wallet_state_manager.get_transaction_status(
tx.name()
)
if len(sent_to) == 0:
await asyncio.sleep(0.1)
continue
status, err = sent_to[0][1], sent_to[0][2]
if status == MempoolInclusionStatus.SUCCESS:
data = {"status": "SUCCESS", "id": wallet_id}
sent = True
break
elif status == MempoolInclusionStatus.PENDING:
assert err is not None
data = {"status": "PENDING", "reason": err, "id": wallet_id}
sent = True
break
elif status == MempoolInclusionStatus.FAILED:
assert err is not None
data = {"status": "FAILED", "reason": err, "id": wallet_id}
sent = True
break
if not sent:
data = {
"status": "FAILED",
"reason": "Timed out. Transaction may or may not have been sent.",
"id": wallet_id,
}
return data
return {
"success": True,
"transaction": tr,
"transaction_id": tr.spend_bundle.name(),
}
async def cc_get_colour(self, request):
wallet_id = int(request["wallet_id"])
wallet: CCWallet = self.service.wallet_state_manager.wallets[wallet_id]
@ -818,13 +793,13 @@ class WalletRpcApi:
tx = await wallet.clawback_rl_coin_transaction()
except Exception as e:
data = {
"status": "FAILED",
"success": False,
"reason": f"Failed to generate signed transaction {e}",
}
return data
if tx is None:
data = {
"status": "FAILED",
"success": False,
"reason": "Failed to generate signed transaction",
}
return data
@ -832,41 +807,14 @@ class WalletRpcApi:
await wallet.push_transaction(tx)
except Exception as e:
data = {
"status": "FAILED",
"success": False,
"reason": f"Failed to push transaction {e}",
}
return data
sent = False
start = time.time()
while time.time() - start < TIMEOUT:
sent_to: List[
Tuple[str, MempoolInclusionStatus, Optional[str]]
] = await self.service.wallet_state_manager.get_transaction_status(
tx.name()
)
if len(sent_to) == 0:
await asyncio.sleep(1)
continue
status, err = sent_to[0][1], sent_to[0][2]
if status == MempoolInclusionStatus.SUCCESS:
data = {"status": "SUCCESS"}
sent = True
break
elif status == MempoolInclusionStatus.PENDING:
assert err is not None
data = {"status": "PENDING", "reason": err}
sent = True
break
elif status == MempoolInclusionStatus.FAILED:
assert err is not None
data = {"status": "FAILED", "reason": err}
sent = True
break
if not sent:
data = {
"status": "FAILED",
"reason": "Timed out. Transaction may or may not have been sent.",
# Transaction may not have been included in the mempool yet. Use get_transaction to check.
return {
"success": True,
"transaction": tx,
"transaction_id": tx.spend_bundle.name(),
}
return data

View File

@ -36,6 +36,9 @@ class WalletRpcClient(RpcClient):
return TransactionRecord.from_json_dict(response["transaction"])
raise Exception(response["reason"])
async def get_next_address(self, wallet_id: str) -> Dict:
return await self.fetch("get_next_address", {"wallet_id": wallet_id})
async def get_transaction(
self, wallet_id: str, transaction_id: bytes32
) -> Optional[TransactionRecord]:

View File

@ -32,7 +32,7 @@ harvester:
pool: {
# Replace this with a real puzzle hash
# xch_target_puzzle_hash: a4259182b4d8e0af21331fc5be2681f953400b6726fa4095e3b91ae8f005a836
# xch_target_address: txch102gkhhzs60grx7cfnpng5n6rjecr89r86l5s8xux2za8k820cxsq64ssdg
logging: *logging
}
@ -50,7 +50,7 @@ farmer:
pool_public_keys: []
# Replace this with a real puzzle hash
# xch_target_puzzle_hash: a4259182b4d8e0af21331fc5be2681f953400b6726fa4095e3b91ae8f005a836
# xch_target_address: txch102gkhhzs60grx7cfnpng5n6rjecr89r86l5s8xux2za8k820cxsq64ssdg
# If True, starts an RPC server at the following port
start_rpc_server: True

View File

@ -319,7 +319,7 @@ class WalletStateManager:
continue
puzzlehash: bytes32 = puzzle.get_tree_hash()
self.log.info(
f"Puzzle at index {index} wid {wallet_id} puzzle hash {puzzlehash.hex()}"
f"Puzzle at index {index} with {wallet_id} puzzle hash {puzzlehash.hex()}"
)
derivation_paths.append(
DerivationRecord(
@ -376,13 +376,13 @@ class WalletStateManager:
"""
self.pending_tx_callback = callback
def state_changed(self, state: str, wallet_id: int = None):
def state_changed(self, state: str, wallet_id: int = None, data_object = {}):
"""
Calls the callback if it's present.
"""
if self.state_changed_callback is None:
return
self.state_changed_callback(state, wallet_id)
self.state_changed_callback(state, wallet_id, data_object)
def tx_pending_changed(self):
"""
@ -698,7 +698,9 @@ class WalletStateManager:
Full node received our transaction, no need to keep it in queue anymore
"""
await self.tx_store.increment_sent(spendbundle_id, name, send_status, error)
self.state_changed("tx_sent")
tx: Optional[TransactionRecord] = self.get_transaction(spendbundle_id)
if tx is not None:
self.state_changed("tx_update", tx.wallet_id, {"transaction": tx})
async def get_send_queue(self) -> List[TransactionRecord]:
"""
@ -1541,13 +1543,3 @@ class WalletStateManager:
if callback_str is not None:
callback = getattr(wallet, callback_str)
await callback(height, header_hash, program, action.id)
async def get_transaction_status(
self, tx_id: bytes32
) -> List[Tuple[str, MempoolInclusionStatus, Optional[str]]]:
tr: Optional[TransactionRecord] = await self.get_transaction(tx_id)
ret_list = []
if tr is not None:
for (name, ss, err) in tr.sent_to:
ret_list.append((name, MempoolInclusionStatus(ss), err))
return ret_list

View File

@ -40,8 +40,9 @@ class TestWalletRpc:
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
wallet_2 = wallet_node_2.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
ph_2 = await wallet.get_new_puzzlehash()
ph_2 = await wallet_2.get_new_puzzlehash()
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
@ -54,6 +55,12 @@ class TestWalletRpc:
for i in range(1, num_blocks - 1)
]
)
initial_funds_eventually = sum(
[
calculate_base_fee(uint32(i)) + calculate_block_reward(uint32(i))
for i in range(1, num_blocks + 1)
]
)
wallet_rpc_api = WalletRpcApi(wallet_node)
@ -81,27 +88,29 @@ class TestWalletRpc:
addr = encode_puzzle_hash(
await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash()
)
tx = await client.send_transaction("1", 1, addr)
tx_amount = 15600000
tx = await client.send_transaction("1", tx_amount, addr)
assert tx is not None
transaction_id = tx.name()
async def tx_in_mempool():
tx = await client.get_transaction("1", transaction_id)
return tx.is_in_mempool()
await time_out_assert(5, tx_in_mempool, True)
await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - 1)
assert (await client.get_wallet_balance("1"))["wallet_balance"]["unconfirmed_wallet_balance"] == initial_funds - 1
await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - tx_amount)
assert (await client.get_wallet_balance("1"))["wallet_balance"]["unconfirmed_wallet_balance"] == initial_funds - tx_amount
assert (await client.get_wallet_balance("1"))["wallet_balance"]["confirmed_wallet_balance"] == initial_funds
for i in range(0, num_blocks * 5):
for i in range(0, 5):
await full_node_1.farm_new_block(FarmNewBlockProtocol(ph_2))
assert (await client.get_wallet_balance("1"))["wallet_balance"]["confirmed_wallet_balance"] == initial_funds - 1
async def eventual_balance():
return (await client.get_wallet_balance("1"))["wallet_balance"]["confirmed_wallet_balance"]
await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount)
await client.get_next_address()
print(await client.get_wallet_balance("1"))
except Exception:
# Checks that the RPC manages to stop the node

View File

@ -21,6 +21,7 @@ from src.util.ints import uint16, uint32
from src.server.start_service import Service
from src.util.make_test_constants import make_test_constants_with_genesis
from tests.time_out_assert import time_out_assert
from src.util.chech32 import encode_puzzle_hash
test_constants, bt = make_test_constants_with_genesis(
@ -269,9 +270,9 @@ async def setup_farmer(
config = load_config(bt.root_path, "config.yaml", "farmer")
config_pool = load_config(bt.root_path, "config.yaml", "pool")
config["xch_target_puzzle_hash"] = bt.farmer_ph.hex()
config["xch_target_address"] = encode_puzzle_hash(bt.farmer_ph).hex()
config["pool_public_keys"] = [bytes(pk).hex() for pk in bt.pool_pubkeys]
config_pool["xch_target_puzzle_hash"] = bt.pool_ph.hex()
config_pool["xch_target_address"] = encode_puzzle_hash(bt.pool_ph).hex()
if full_node_port:
connect_peers = [PeerInfo(self_hostname, full_node_port)]
else: