Merge branch 'integration' of github.com:Chia-Network/chia-blockchain into integration

This commit is contained in:
Mariano Sorgente 2020-03-06 13:41:07 +09:00
commit 3f0c59336f
No known key found for this signature in database
GPG Key ID: 0F866338C369278C
12 changed files with 500 additions and 334 deletions

View File

@ -47,6 +47,7 @@ wcwidth==0.1.8
yarl==1.4.2
zipp==2.0.0
sortedcontainers==2.1.0
websockets==8.1.0
-e lib/chiapos
-e lib/bip158
-e lib/py-setproctitle

View File

@ -81,7 +81,7 @@ class RequestHeader:
@cbor_message
class RespondHeader:
header_block: HeaderBlock
bip158_filter: Optional[bytes]
transactions_filter: Optional[bytes]
@dataclass(frozen=True)

View File

@ -2,7 +2,7 @@ const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const WebSocket = require('ws');
/*************************************************************
* py process
@ -10,7 +10,7 @@ const path = require('path')
const PY_DIST_FOLDER = 'pydist'
const PY_FOLDER = 'rpc'
const PY_MODULE = 'rpc_wallet' // without .py suffix
const PY_MODULE = 'websocket_server' // without .py suffix
let pyProc = null
let pyPort = null
@ -68,7 +68,14 @@ app.on('will-quit', exitPyProc)
let mainWindow = null
const createWindow = () => {
mainWindow = new BrowserWindow({width: 1500, height: 800})
console.log(process.versions)
mainWindow = new BrowserWindow({
width: 1500,
height: 800,
webPreferences: {
nodeIntegration: true
},})
mainWindow.loadURL(require('url').format({
pathname: path.join(__dirname, 'wallet-dark.html'),
protocol: 'file:',

View File

@ -128,6 +128,11 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -2900,6 +2905,14 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",

View File

@ -16,7 +16,8 @@
"dialogs": "^2.0.1",
"electron-rebuild": "^1.10.0",
"jquery": "^3.4.1",
"qrcode": "^1.4.4"
"qrcode": "^1.4.4",
"ws": "^6.2.1"
},
"devDependencies": {
"electron": "^1.8.8",

View File

@ -1,9 +1,11 @@
//import { createRPC } from '@erebos/rpc-http-browser'
const host = "ws://127.0.0.1:9256"
const jquery = require('jquery')
var QRCode = require('qrcode')
var canvas = document.getElementById('qr_canvas')
const Dialogs = require('dialogs')
const dialogs = Dialogs()
const WebSocket = require('ws');
var ws = new WebSocket(host);
let send = document.querySelector('#send')
let new_address = document.querySelector('#new_address')
@ -16,173 +18,209 @@ let red_checkmark = "<i class=\"icon ion-md-close-circle-outline red\"></i>"
var myBalance = 0
var myUnconfirmedBalance = 0
send.addEventListener('click', () => {
puzzlehash = receiver_address.value
amount_value = amount.value
data = {"puzzlehash": puzzlehash, "amount": amount_value}
json_data = JSON.stringify(data)
jquery.ajax({
type: 'POST',
url: 'http://127.0.0.1:9256/send_transaction',
data: json_data,
dataType: 'json'
})
.done(function(response) {
console.log(response)
success = response["success"]
if (!success) {
dialogs.alert("You don\'t have enough chia for this transactions", ok => {
})
return
}
})
.fail(function(data) {
console.log(data)
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function set_callbacks(socket) {
/*
Sets callbacks for socket events
*/
socket.on('open', function open() {
var msg = {"command": "start_server"}
ws.send(JSON.stringify(msg));
});
socket.on('message', function incoming(incoming) {
var message = JSON.parse(incoming);
var command = message["command"];
var data = message["data"];
console.log("Received command: " + command);
if (data) {
console.log("Received message data: " + JSON.stringify(data));
}
if (command == "start_server") {
get_new_puzzlehash();
get_transactions();
get_wallet_balance();
} else if (command == "get_next_puzzle_hash") {
get_new_puzzlehash_response(data);
} else if (command == "get_wallet_balance") {
get_wallet_balance_response(data);
} else if (command == "send_transaction") {
send_transaction_response(data);
} else if (command == "get_transactions") {
get_transactions_response(data);
}
});
socket.on('error', function clear() {
console.log("lol");
connect(1);
});
}
set_callbacks(ws);
async function connect(timeout) {
/*
Tries to connect to the host after a timeout
*/
await sleep(timeout);
ws = new WebSocket(host);
set_callbacks(ws);
}
send.addEventListener('click', () => {
/*
Called when send button in ui is pressed.
*/
puzzlehash = receiver_address.value;
amount_value = amount.value;
data = {
"puzzlehash": puzzlehash,
"amount": amount_value
}
request = {
"command": "send_transaction",
"data": data
}
json_data = JSON.stringify(request);
ws.send(json_data);
})
function send_transaction_response(response) {
/*
Called when response is received for send_transaction request
*/
success = response["success"];
if (!success) {
dialogs.alert("You don\'t have enough chia for this transactions", ok => {
})
return
}
}
new_address.addEventListener('click', () => {
console.log("new address requesting")
get_new_puzzlehash(0)
/*
Called when new address button is pressed.
*/
console.log("new address requesting");
get_new_puzzlehash(0);
})
copy.addEventListener("click", () => {
let puzzle_holder = document.querySelector("#puzzle_holder")
/*
Called when copy button is pressed
*/
let puzzle_holder = document.querySelector("#puzzle_holder");
puzzle_holder.select();
/* Copy the text inside the text field */
/* Copy the text inside the text field */
document.execCommand("copy");
})
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
async function get_new_puzzlehash() {
/*
Sends websocket request for new puzzle_hash
*/
data = {
"command": "get_next_puzzle_hash",
}
json_data = JSON.stringify(data);
ws.send(json_data);
}
async function get_new_puzzlehash(timeout) {
//wait for wallet.py to start up
await sleep(timeout)
jquery.ajax({
type: 'POST',
url: 'http://127.0.0.1:9256/get_next_puzzle_hash',
dataType: 'json'
function get_new_puzzlehash_response(response) {
/*
Called when response is received for get_new_puzzle_hash request
*/
let puzzle_holder = document.querySelector("#puzzle_holder");
puzzle_holder.value = response["puzzlehash"];
QRCode.toCanvas(canvas, response["puzzlehash"], function (error) {
if (error) console.error(error)
console.log('success!');
})
.done(function(response) {
console.log(response)
let puzzle_holder = document.querySelector("#puzzle_holder")
puzzle_holder.value = response["puzzlehash"]
QRCode.toCanvas(canvas, response["puzzlehash"], function (error) {
if (error) console.error(error)
console.log('success!');
})
})
.fail(function(data) {
console.log(data)
get_new_puzzlehash(300)
});
}
async function get_wallet_balance(timeout) {
//wait for wallet.py to start up
await sleep(timeout)
jquery.ajax({
type: 'POST',
url: 'http://127.0.0.1:9256/get_wallet_balance',
dataType: 'json'
})
.done(function(response) {
console.log(response)
})
.fail(function(data) {
console.log(data)
get_wallet_balance(1000)
});
async function get_wallet_balance() {
/*
Sends websocket request to get wallet balance
*/
data = {
"command": "get_wallet_balance",
}
json_data = JSON.stringify(data);
ws.send(json_data);
}
async function get_transactions(timeout) {
//wait for wallet.py to start up
await sleep(timeout)
jquery.ajax({
type: 'POST',
url: 'http://127.0.0.1:9256/get_transactions',
dataType: 'json'
})
.done(function(response) {
console.log(response)
clean_table()
function get_wallet_balance_response(response) {
console.log("update balance");
}
for (var i = 0; i < response.txs.length; i++) {
var tx = JSON.parse(response.txs[i]);
console.log(tx);
var row = table.insertRow(0);
var cell_type = row.insertCell(0);
var cell_to = row.insertCell(1);
var cell_date = row.insertCell(2);
var cell_status = row.insertCell(3);
var cell_amount = row.insertCell(4);
var cell_fee = row.insertCell(5);
//type of transaction
if (tx["incoming"]) {
cell_type.innerHTML = "Incoming"
} else {
cell_type.innerHTML = "Outgoing"
}
// Receiving puzzle hash
cell_to.innerHTML = tx["to_puzzle_hash"]
async function get_transactions() {
/*
Sends websocket request to get transactions
*/
data = {
"command": "get_transactions",
}
json_data = JSON.stringify(data);
ws.send(json_data);
}
// Date
var date = new Date(parseInt(tx["created_at_time"]) * 1000);
cell_date.innerHTML = "" + date
function get_transactions_response(response) {
/*
Called when response is received for get_transactions request
*/
clean_table()
// Confirmation status
if (tx["confirmed"]) {
index = tx["confirmed_block_index"]
cell_status.innerHTML = "Confirmed" + green_checkmark +"</br>" + "Block: " + index;
} else {
cell_status.innerHTML = "Pending " + red_checkmark;
}
for (var i = 0; i < response.txs.length; i++) {
var tx = JSON.parse(response.txs[i]);
console.log(tx);
var row = table.insertRow(0);
var cell_type = row.insertCell(0);
var cell_to = row.insertCell(1);
var cell_date = row.insertCell(2);
var cell_status = row.insertCell(3);
var cell_amount = row.insertCell(4);
var cell_fee = row.insertCell(5);
//type of transaction
if (tx["incoming"]) {
cell_type.innerHTML = "Incoming";
} else {
cell_type.innerHTML = "Outgoing";
}
// Receiving puzzle hash
cell_to.innerHTML = tx["to_puzzle_hash"];
// Amount and Fee
cell_amount.innerHTML = tx["amount"];
cell_fee.innerHTML = tx["fee_amount"]
// Date
var date = new Date(parseInt(tx["created_at_time"]) * 1000);
cell_date.innerHTML = "" + date;
// Confirmation status
if (tx["confirmed"]) {
index = tx["confirmed_block_index"];
cell_status.innerHTML = "Confirmed" + green_checkmark +"</br>" + "Block: " + index;
} else {
cell_status.innerHTML = "Pending " + red_checkmark;
}
})
.fail(function(data) {
console.log(data)
get_transactions(1000)
});
}
async function get_server_ready(timeout) {
//wait for wallet.py to start up
await sleep(timeout)
jquery.ajax({
type: 'POST',
url: 'http://127.0.0.1:9256/get_server_ready',
dataType: 'json'
})
.done(function(response) {
console.log(response)
success = response["success"]
if (success) {
get_transactions(0)
get_new_puzzlehash(0)
get_wallet_balance(0)
}
})
.fail(function(data) {
console.log(data)
get_server_ready(100)
});
// Amount and Fee
cell_amount.innerHTML = tx["amount"];
cell_fee.innerHTML = tx["fee_amount"];
}
}
function clean_table() {
while (table.rows.length > 0) {
table.deleteRow(0);
table.deleteRow(0);
}
}
clean_table()
get_server_ready(100)
clean_table();

View File

@ -1,160 +0,0 @@
import asyncio
import dataclasses
import json
from typing import Any
from aiohttp import web
from src.server.outbound_message import NodeType
from src.server.server import ChiaServer
from src.types.peer_info import PeerInfo
from src.util.config import load_config
from src.wallet.wallet_node import WalletNode
class EnhancedJSONEncoder(json.JSONEncoder):
"""
Encodes bytes as hex strings with 0x, and converts all dataclasses to json.
"""
def default(self, o: Any):
if dataclasses.is_dataclass(o):
return o.to_json()
elif hasattr(type(o), "__bytes__"):
return f"0x{bytes(o).hex()}"
return super().default(o)
def obj_to_response(o: Any) -> web.Response:
"""
Converts a python object into json.
"""
json_str = json.dumps(o, cls=EnhancedJSONEncoder, sort_keys=True)
return web.Response(body=json_str, content_type="application/json")
class RpcWalletApiHandler:
"""
Implementation of full node RPC API.
Note that this is not the same as the peer protocol, or wallet protocol (which run Chia's
protocol on top of TCP), it's a separate protocol on top of HTTP thats provides easy access
to the full node.
"""
def __init__(self, wallet_node: WalletNode):
self.wallet_node = wallet_node
async def get_next_puzzle_hash(self, request) -> web.Response:
"""
Returns a new puzzlehash
"""
puzzlehash = (await self.wallet_node.wallet.get_new_puzzlehash()).hex()
response = {
"puzzlehash": puzzlehash,
}
return obj_to_response(response)
async def send_transaction(self, request) -> web.Response:
request_data = await request.json()
if "amount" in request_data and "puzzlehash" in request_data:
amount = int(request_data["amount"])
puzzlehash = request_data["puzzlehash"]
tx = await self.wallet_node.wallet.generate_signed_transaction(
amount, puzzlehash
)
if tx is None:
response = {"success": False}
return obj_to_response(response)
await self.wallet_node.wallet.push_transaction(tx)
response = {"success": True}
return obj_to_response(response)
response = {"success": False}
return obj_to_response(response)
async def get_server_ready(self, request) -> web.Response:
response = {"success": True}
return obj_to_response(response)
async def get_transactions(self, request) -> web.Response:
transactions = (
await self.wallet_node.wallet_state_manager.get_all_transactions()
)
response = {"success": True, "txs": transactions}
return obj_to_response(response)
async def get_wallet_balance(self, request) -> web.Response:
balance = await self.wallet_node.wallet.get_confirmed_balance()
pending_balance = await self.wallet_node.wallet.get_unconfirmed_balance()
response = {
"success": True,
"confirmed_wallet_balance": balance,
"unconfirmed_wallet_balance": pending_balance,
}
return obj_to_response(response)
async def start_rpc_server():
"""
Starts an HTTP server with the following RPC methods, to be used by local clients to
query the node.
"""
config = load_config("config.yaml", "wallet")
try:
key_config = load_config("keys.yaml")
except FileNotFoundError:
raise RuntimeError(
"Keys not generated. Run python3 ./scripts/regenerate_keys.py."
)
wallet_node = await WalletNode.create(config, key_config)
server = ChiaServer(9257, wallet_node, NodeType.WALLET)
wallet_node.set_server(server)
full_node_peer = PeerInfo(
config["full_node_peer"]["host"], config["full_node_peer"]["port"]
)
_ = await server.start_server("127.0.0.1", None)
await asyncio.sleep(1)
_ = await server.start_client(full_node_peer, None)
handler = RpcWalletApiHandler(wallet_node)
app = web.Application()
app.add_routes(
[
web.post("/get_next_puzzle_hash", handler.get_next_puzzle_hash),
web.post("/send_transaction", handler.send_transaction),
web.post("/get_server_ready", handler.get_server_ready),
web.post("/get_transactions", handler.get_transactions),
web.post("/get_wallet_balance", handler.get_wallet_balance),
]
)
runner = web.AppRunner(app, access_log=None)
await runner.setup()
site = web.TCPSite(runner, "localhost", 9256)
await site.start()
await server.await_closed()
async def cleanup():
await runner.cleanup()
return cleanup
async def main():
cleanup = await start_rpc_server()
print("start running on {}")
await cleanup()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -0,0 +1,169 @@
import asyncio
import dataclasses
import json
import websockets
from typing import Any, Dict
from aiohttp import web
from src.server.outbound_message import NodeType
from src.server.server import ChiaServer
from src.types.peer_info import PeerInfo
from src.util.config import load_config
from src.wallet.wallet_node import WalletNode
class EnhancedJSONEncoder(json.JSONEncoder):
"""
Encodes bytes as hex strings with 0x, and converts all dataclasses to json.
"""
def default(self, o: Any):
if dataclasses.is_dataclass(o):
return o.to_json()
elif hasattr(type(o), "__bytes__"):
return f"0x{bytes(o).hex()}"
return super().default(o)
def obj_to_response(o: Any) -> str:
"""
Converts a python object into json.
"""
json_str = json.dumps(o, cls=EnhancedJSONEncoder, sort_keys=True)
return json_str
def format_response(command: str, response_data: Dict[str, Any]):
"""
Formats the response into standard format used between renderer.js and here
"""
response = {"command": command, "data": response_data}
json_str = obj_to_response(response)
return json_str
class WebSocketServer:
def __init__(self, wallet_node: WalletNode):
self.wallet_node = wallet_node
async def get_next_puzzle_hash(self, websocket, response_api) -> web.Response:
"""
Returns a new puzzlehash
"""
puzzlehash = (await self.wallet_node.wallet.get_new_puzzlehash()).hex()
data = {
"puzzlehash": puzzlehash,
}
await websocket.send(format_response(response_api, data))
async def send_transaction(self, websocket, request, response_api):
if "amount" in request and "puzzlehash" in request:
amount = int(request["amount"])
puzzlehash = request["puzzlehash"]
tx = await self.wallet_node.wallet.generate_signed_transaction(
amount, puzzlehash
)
if tx is None:
data = {"success": False}
return await websocket.send(format_response(response_api, data))
await self.wallet_node.wallet.push_transaction(tx)
data = {"success": True}
return await websocket.send(format_response(response_api, data))
data = {"success": False}
await websocket.send(format_response(response_api, data))
async def server_ready(self, websocket, response_api):
response = {"success": True}
await websocket.send(format_response(response_api, response))
async def get_transactions(self, websocket, response_api):
transactions = (
await self.wallet_node.wallet_state_manager.get_all_transactions()
)
response = {"success": True, "txs": transactions}
await websocket.send(format_response(response_api, response))
async def get_wallet_balance(self, websocket, response_api):
balance = await self.wallet_node.wallet.get_confirmed_balance()
pending_balance = await self.wallet_node.wallet.get_unconfirmed_balance()
response = {
"success": True,
"confirmed_wallet_balance": balance,
"unconfirmed_wallet_balance": pending_balance,
}
await websocket.send(format_response(response_api, response))
async def handle_message(self, websocket, path):
"""
This function gets called when new message is received via websocket.
"""
async for message in websocket:
print(message)
decoded = json.loads(message)
command = decoded["command"]
data = None
if "data" in decoded:
data = decoded["data"]
if command == "start_server":
await self.server_ready(websocket, command)
elif command == "get_wallet_balance":
await self.get_wallet_balance(websocket, command)
elif command == "send_transaction":
await self.send_transaction(websocket, data, command)
elif command == "get_next_puzzle_hash":
await self.get_next_puzzle_hash(websocket, command)
elif command == "get_transactions":
await self.get_transactions(websocket, command)
else:
response = {"error": f"unknown_command {command}"}
await websocket.send(obj_to_response(response))
async def start_websocket_server():
"""
Starts WalletNode, WebSocketServer, and ChiaServer
"""
config = load_config("config.yaml", "wallet")
try:
key_config = load_config("keys.yaml")
except FileNotFoundError:
raise RuntimeError(
"Keys not generated. Run python3 ./scripts/regenerate_keys.py."
)
wallet_node = await WalletNode.create(config, key_config)
handler = WebSocketServer(wallet_node)
server = ChiaServer(9257, wallet_node, NodeType.WALLET)
wallet_node.set_server(server)
full_node_peer = PeerInfo(
config["full_node_peer"]["host"], config["full_node_peer"]["port"]
)
_ = await server.start_server("127.0.0.1", None)
await asyncio.sleep(1)
_ = await server.start_client(full_node_peer, None)
await websockets.serve(handler.handle_message, "localhost", 9256)
await server.await_closed()
async def main():
await start_websocket_server()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,4 +1,4 @@
from typing import Dict, Optional, List, Tuple
from typing import Dict, Optional, List, Tuple, Set
import clvm
from blspy import ExtendedPrivateKey, PublicKey
import logging
@ -6,6 +6,7 @@ from src.server.outbound_message import OutboundMessage, NodeType, Message, Deli
from src.server.server import ChiaServer
from src.protocols import full_node_protocol
from src.types.hashable.BLSSignature import BLSSignature
from src.types.hashable.coin import Coin
from src.types.hashable.coin_solution import CoinSolution
from src.types.hashable.program import Program
from src.types.hashable.spend_bundle import SpendBundle
@ -42,9 +43,6 @@ class Wallet:
# TODO Don't allow user to send tx until wallet is synced
synced: bool
# Queue of SpendBundles that FullNode hasn't acked yet.
send_queue: Dict[bytes32, SpendBundle]
@staticmethod
async def create(
config: Dict,
@ -101,20 +99,20 @@ class Wallet:
self.server = server
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None):
ret = []
condition_list = []
if primaries:
for primary in primaries:
ret.append(
condition_list.append(
make_create_coin_condition(primary["puzzlehash"], primary["amount"])
)
if consumed:
for coin in consumed:
ret.append(make_assert_coin_consumed_condition(coin))
condition_list.append(make_assert_coin_consumed_condition(coin))
if min_time > 0:
ret.append(make_assert_time_exceeds_condition(min_time))
condition_list.append(make_assert_time_exceeds_condition(min_time))
if me:
ret.append(make_assert_my_coin_id_condition(me["id"]))
return clvm.to_sexp_f([puzzle_for_conditions(ret), []])
condition_list.append(make_assert_my_coin_id_condition(me["id"]))
return clvm.to_sexp_f([puzzle_for_conditions(condition_list), []])
async def get_keys(
self, hash: bytes32
@ -131,20 +129,33 @@ class Wallet:
async def generate_unsigned_transaction(
self, amount: int, newpuzzlehash: bytes32, fee: int = 0
) -> List[Tuple[Program, CoinSolution]]:
utxos = await self.wallet_state_manager.select_coins(amount + fee)
"""
Generates a unsigned transaction in form of List(Puzzle, Solutions)
"""
utxos: Optional[Set[Coin]] = await self.wallet_state_manager.select_coins(
amount + fee
)
if utxos is None:
return []
spends: List[Tuple[Program, CoinSolution]] = []
output_created = False
spend_value = sum([coin.amount for coin in utxos])
change = spend_value - amount - fee
spends: List[Tuple[Program, CoinSolution]] = []
output_created = False
for coin in utxos:
# Get keys for puzzle_hash
puzzle_hash = coin.puzzle_hash
maybe = await self.get_keys(puzzle_hash)
if not maybe:
return []
# Get puzzle for pubkey
pubkey, secretkey = maybe
puzzle: Program = puzzle_for_pk(pubkey.serialize())
# Only one coin creates outputs
if output_created is False:
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
if change > 0:
@ -154,29 +165,38 @@ class Wallet:
solution = self.make_solution(primaries=primaries)
output_created = True
else:
# TODO coin consumed condition should be removed
solution = self.make_solution(consumed=[coin.name()])
spends.append((puzzle, CoinSolution(coin, solution)))
return spends
async def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
sigs = []
signatures = []
for puzzle, solution in spends:
# Get keys
keys = await self.get_keys(solution.coin.puzzle_hash)
if not keys:
return None
pubkey, secretkey = keys
secretkey = BLSPrivateKey(secretkey)
code_ = [puzzle, solution.solution]
sexp = clvm.to_sexp_f(code_)
# Get AGGSIG conditions
err, con, cost = conditions_for_solution(sexp)
if err or not con:
return None
conditions_dict = conditions_by_opcode(con)
for _ in hash_key_pairs_for_conditions_dict(conditions_dict):
signature = secretkey.sign(_.message_hash)
sigs.append(signature)
aggsig = BLSSignature.aggregate(sigs)
# Create signature
for pk_message in hash_key_pairs_for_conditions_dict(conditions_dict):
signature = secretkey.sign(pk_message.message_hash)
signatures.append(signature)
# Aggregate signatures
aggsig = BLSSignature.aggregate(signatures)
solution_list: List[CoinSolution] = [
CoinSolution(
coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])
@ -184,6 +204,7 @@ class Wallet:
for (puzzle, coin_solution) in spends
]
spend_bundle = SpendBundle(solution_list, aggsig)
return spend_bundle
async def generate_signed_transaction(

View File

@ -11,6 +11,7 @@ from src.util.ints import uint32
from src.util.api_decorators import api_request
from src.wallet.wallet import Wallet
from src.wallet.wallet_state_manager import WalletStateManager
from src.wallet.block_record import BlockRecord
class WalletNode:
@ -22,6 +23,7 @@ class WalletNode:
log: logging.Logger
wallet: Wallet
constants: Dict
short_sync_threshold: int
@staticmethod
async def create(
@ -49,6 +51,7 @@ class WalletNode:
self.wallet = await Wallet.create(config, key_config, self.wallet_state_manager)
self.server = None
self.short_sync_threshold = 10
return self
@ -105,7 +108,7 @@ class WalletNode:
if request.weight < lca.weight:
return
if int(request.height) - int(lca.height) > 10:
if int(request.height) - int(lca.height) > self.short_sync_threshold:
try:
# Performs sync, and catch exceptions so we don't close the connection
async for ret_msg in self._sync():
@ -126,15 +129,60 @@ class WalletNode:
@api_request
async def respond_header(self, response: wallet_protocol.RespondHeader):
# TODO(mariano): implement
block = response.header_block
# 0. If we already have, return
if block.header_hash in self.wallet_state_manager.block_records:
return
lca = self.wallet_state_manager.block_records[self.wallet_state_manager.lca]
# 1. If disconnected and close, get parent header and return
if block.prev_header_hash not in self.wallet_state_manager.block_records:
if block.height - lca.height < self.short_sync_threshold:
header_request = wallet_protocol.RequestHeader(
uint32(block.height - 1), block.prev_header_hash,
)
yield OutboundMessage(
NodeType.FULL_NODE,
Message("request_header", header_request),
Delivery.RESPOND,
)
return
# 2. If we have transactions, fetch adds/deletes
# adds_deletes = await self.wallet_state_manager.filter_additions_removals()
# 3. If we don't have, don't fetch
# 4. If we have the next header cached, process it
pass
if response.transactions_filter is not None:
(
additions,
removals,
) = await self.wallet_state_manager.get_filter_additions_removals(
response.transactions_filter
)
if len(additions) > 0:
request_a = wallet_protocol.RequestAdditions(
block.height, block.header_hash, additions
)
yield OutboundMessage(
NodeType.FULL_NODE,
Message("request_additions", request_a),
Delivery.RESPOND,
)
# Request additions
if len(removals) > 0:
request_r = wallet_protocol.RequestRemovals(
block.height, block.header_hash, removals
)
yield OutboundMessage(
NodeType.FULL_NODE,
Message("request_removals", request_r),
Delivery.RESPOND,
)
else:
block_record = BlockRecord(block.header_hash, block.prev_header_hash, block.height, block.weight, [], [])
res = await self.wallet_state_manager.receive_block(block_record, block)
# 3. If we don't have, don't fetch
# 4. If we have the next header cached, process it
pass
@api_request
async def reject_header_request(

View File

@ -365,18 +365,18 @@ class WalletStateManager:
] = await self.wallet_store.get_coin_records_by_spent(False)
my_puzzle_hashes = await self.puzzle_store.get_all_puzzle_hashes()
removals_of_interests: bytes32 = []
removals_of_interest: bytes32 = []
additions_of_interest: bytes32 = []
for record in my_coin_records:
if tx_filter.Match(bytearray(record.name())):
removals_of_interests.append(record.name())
removals_of_interest.append(record.name())
for puzzle_hash in my_puzzle_hashes:
if tx_filter.Match(bytearray(puzzle_hash)):
additions_of_interest.append(puzzle_hash)
return (removals_of_interests, additions_of_interest)
return (additions_of_interest, removals_of_interest)
async def get_relevant_additions(self, additions: List[Coin]) -> List[Coin]:
""" Returns the list of coins that are relevant to us.(We can spend them) """
@ -407,9 +407,14 @@ class WalletStateManager:
Rolls back and updates the coin_store and transaction store. It's possible this height
is the tip, or even beyond the tip.
"""
print("Doing reorg...")
await self.wallet_store.rollback_lca_to_block(index)
# TODO Straya
reorged: List[TransactionRecord] = await self.tx_store.get_transaction_above(
index
)
await self.tx_store.rollback_to_block(index)
await self.retry_sending_after_reorg(reorged)
async def retry_sending_after_reorg(self, records: List[TransactionRecord]):
"""

View File

@ -227,3 +227,26 @@ class WalletTransactionStore:
records.append(record)
return records
async def get_transaction_above(self, height: uint32) -> List[TransactionRecord]:
cursor = await self.db_connection.execute(
"SELECT * from transaction_record WHERE confirmed_at_index>?", (height,)
)
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TransactionRecord.from_bytes(row[0])
records.append(record)
return records
async def rollback_to_block(self, block_index):
# Delete from storage
c1 = await self.db_connection.execute(
"DELETE FROM transaction_record WHERE confirmed_at_index>?", (block_index,)
)
await c1.close()
await self.db_connection.commit()