Merge branch 'integration' of github.com:Chia-Network/chia-blockchain into integration
This commit is contained in:
commit
3f0c59336f
|
@ -47,6 +47,7 @@ wcwidth==0.1.8
|
||||||
yarl==1.4.2
|
yarl==1.4.2
|
||||||
zipp==2.0.0
|
zipp==2.0.0
|
||||||
sortedcontainers==2.1.0
|
sortedcontainers==2.1.0
|
||||||
|
websockets==8.1.0
|
||||||
-e lib/chiapos
|
-e lib/chiapos
|
||||||
-e lib/bip158
|
-e lib/bip158
|
||||||
-e lib/py-setproctitle
|
-e lib/py-setproctitle
|
||||||
|
|
|
@ -81,7 +81,7 @@ class RequestHeader:
|
||||||
@cbor_message
|
@cbor_message
|
||||||
class RespondHeader:
|
class RespondHeader:
|
||||||
header_block: HeaderBlock
|
header_block: HeaderBlock
|
||||||
bip158_filter: Optional[bytes]
|
transactions_filter: Optional[bytes]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|
|
@ -2,7 +2,7 @@ const electron = require('electron')
|
||||||
const app = electron.app
|
const app = electron.app
|
||||||
const BrowserWindow = electron.BrowserWindow
|
const BrowserWindow = electron.BrowserWindow
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
/*************************************************************
|
/*************************************************************
|
||||||
* py process
|
* py process
|
||||||
|
@ -10,7 +10,7 @@ const path = require('path')
|
||||||
|
|
||||||
const PY_DIST_FOLDER = 'pydist'
|
const PY_DIST_FOLDER = 'pydist'
|
||||||
const PY_FOLDER = 'rpc'
|
const PY_FOLDER = 'rpc'
|
||||||
const PY_MODULE = 'rpc_wallet' // without .py suffix
|
const PY_MODULE = 'websocket_server' // without .py suffix
|
||||||
|
|
||||||
let pyProc = null
|
let pyProc = null
|
||||||
let pyPort = null
|
let pyPort = null
|
||||||
|
@ -68,7 +68,14 @@ app.on('will-quit', exitPyProc)
|
||||||
let mainWindow = null
|
let mainWindow = null
|
||||||
|
|
||||||
const createWindow = () => {
|
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({
|
mainWindow.loadURL(require('url').format({
|
||||||
pathname: path.join(__dirname, 'wallet-dark.html'),
|
pathname: path.join(__dirname, 'wallet-dark.html'),
|
||||||
protocol: 'file:',
|
protocol: 'file:',
|
||||||
|
|
|
@ -128,6 +128,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
"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": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"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": {
|
"xmlbuilder": {
|
||||||
"version": "9.0.7",
|
"version": "9.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
"dialogs": "^2.0.1",
|
"dialogs": "^2.0.1",
|
||||||
"electron-rebuild": "^1.10.0",
|
"electron-rebuild": "^1.10.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"qrcode": "^1.4.4"
|
"qrcode": "^1.4.4",
|
||||||
|
"ws": "^6.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^1.8.8",
|
"electron": "^1.8.8",
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
//import { createRPC } from '@erebos/rpc-http-browser'
|
const host = "ws://127.0.0.1:9256"
|
||||||
const jquery = require('jquery')
|
const jquery = require('jquery')
|
||||||
var QRCode = require('qrcode')
|
var QRCode = require('qrcode')
|
||||||
var canvas = document.getElementById('qr_canvas')
|
var canvas = document.getElementById('qr_canvas')
|
||||||
const Dialogs = require('dialogs')
|
const Dialogs = require('dialogs')
|
||||||
const dialogs = Dialogs()
|
const dialogs = Dialogs()
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
var ws = new WebSocket(host);
|
||||||
|
|
||||||
let send = document.querySelector('#send')
|
let send = document.querySelector('#send')
|
||||||
let new_address = document.querySelector('#new_address')
|
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 myBalance = 0
|
||||||
var myUnconfirmedBalance = 0
|
var myUnconfirmedBalance = 0
|
||||||
|
|
||||||
send.addEventListener('click', () => {
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
puzzlehash = receiver_address.value
|
setTimeout(resolve, ms);
|
||||||
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 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', () => {
|
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", () => {
|
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();
|
puzzle_holder.select();
|
||||||
/* Copy the text inside the text field */
|
/* Copy the text inside the text field */
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
})
|
})
|
||||||
|
|
||||||
function sleep(ms) {
|
async function get_new_puzzlehash() {
|
||||||
return new Promise((resolve) => {
|
/*
|
||||||
setTimeout(resolve, ms);
|
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) {
|
function get_new_puzzlehash_response(response) {
|
||||||
//wait for wallet.py to start up
|
/*
|
||||||
await sleep(timeout)
|
Called when response is received for get_new_puzzle_hash request
|
||||||
jquery.ajax({
|
*/
|
||||||
type: 'POST',
|
let puzzle_holder = document.querySelector("#puzzle_holder");
|
||||||
url: 'http://127.0.0.1:9256/get_next_puzzle_hash',
|
puzzle_holder.value = response["puzzlehash"];
|
||||||
dataType: 'json'
|
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) {
|
async function get_wallet_balance() {
|
||||||
//wait for wallet.py to start up
|
/*
|
||||||
await sleep(timeout)
|
Sends websocket request to get wallet balance
|
||||||
jquery.ajax({
|
*/
|
||||||
type: 'POST',
|
data = {
|
||||||
url: 'http://127.0.0.1:9256/get_wallet_balance',
|
"command": "get_wallet_balance",
|
||||||
dataType: 'json'
|
}
|
||||||
})
|
json_data = JSON.stringify(data);
|
||||||
.done(function(response) {
|
ws.send(json_data);
|
||||||
console.log(response)
|
|
||||||
})
|
|
||||||
.fail(function(data) {
|
|
||||||
console.log(data)
|
|
||||||
get_wallet_balance(1000)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get_transactions(timeout) {
|
function get_wallet_balance_response(response) {
|
||||||
//wait for wallet.py to start up
|
console.log("update balance");
|
||||||
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()
|
|
||||||
|
|
||||||
for (var i = 0; i < response.txs.length; i++) {
|
async function get_transactions() {
|
||||||
var tx = JSON.parse(response.txs[i]);
|
/*
|
||||||
console.log(tx);
|
Sends websocket request to get transactions
|
||||||
var row = table.insertRow(0);
|
*/
|
||||||
var cell_type = row.insertCell(0);
|
data = {
|
||||||
var cell_to = row.insertCell(1);
|
"command": "get_transactions",
|
||||||
var cell_date = row.insertCell(2);
|
}
|
||||||
var cell_status = row.insertCell(3);
|
json_data = JSON.stringify(data);
|
||||||
var cell_amount = row.insertCell(4);
|
ws.send(json_data);
|
||||||
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"]
|
|
||||||
|
|
||||||
// Date
|
function get_transactions_response(response) {
|
||||||
var date = new Date(parseInt(tx["created_at_time"]) * 1000);
|
/*
|
||||||
cell_date.innerHTML = "" + date
|
Called when response is received for get_transactions request
|
||||||
|
*/
|
||||||
|
clean_table()
|
||||||
|
|
||||||
// Confirmation status
|
for (var i = 0; i < response.txs.length; i++) {
|
||||||
if (tx["confirmed"]) {
|
var tx = JSON.parse(response.txs[i]);
|
||||||
index = tx["confirmed_block_index"]
|
console.log(tx);
|
||||||
cell_status.innerHTML = "Confirmed" + green_checkmark +"</br>" + "Block: " + index;
|
var row = table.insertRow(0);
|
||||||
} else {
|
var cell_type = row.insertCell(0);
|
||||||
cell_status.innerHTML = "Pending " + red_checkmark;
|
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
|
// Date
|
||||||
cell_amount.innerHTML = tx["amount"];
|
var date = new Date(parseInt(tx["created_at_time"]) * 1000);
|
||||||
cell_fee.innerHTML = tx["fee_amount"]
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
// Amount and Fee
|
||||||
.fail(function(data) {
|
cell_amount.innerHTML = tx["amount"];
|
||||||
console.log(data)
|
cell_fee.innerHTML = tx["fee_amount"];
|
||||||
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)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_table() {
|
function clean_table() {
|
||||||
while (table.rows.length > 0) {
|
while (table.rows.length > 0) {
|
||||||
table.deleteRow(0);
|
table.deleteRow(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clean_table()
|
|
||||||
get_server_ready(100)
|
|
||||||
|
|
||||||
|
clean_table();
|
||||||
|
|
|
@ -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())
|
|
|
@ -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())
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Dict, Optional, List, Tuple
|
from typing import Dict, Optional, List, Tuple, Set
|
||||||
import clvm
|
import clvm
|
||||||
from blspy import ExtendedPrivateKey, PublicKey
|
from blspy import ExtendedPrivateKey, PublicKey
|
||||||
import logging
|
import logging
|
||||||
|
@ -6,6 +6,7 @@ from src.server.outbound_message import OutboundMessage, NodeType, Message, Deli
|
||||||
from src.server.server import ChiaServer
|
from src.server.server import ChiaServer
|
||||||
from src.protocols import full_node_protocol
|
from src.protocols import full_node_protocol
|
||||||
from src.types.hashable.BLSSignature import BLSSignature
|
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.coin_solution import CoinSolution
|
||||||
from src.types.hashable.program import Program
|
from src.types.hashable.program import Program
|
||||||
from src.types.hashable.spend_bundle import SpendBundle
|
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
|
# TODO Don't allow user to send tx until wallet is synced
|
||||||
synced: bool
|
synced: bool
|
||||||
|
|
||||||
# Queue of SpendBundles that FullNode hasn't acked yet.
|
|
||||||
send_queue: Dict[bytes32, SpendBundle]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def create(
|
async def create(
|
||||||
config: Dict,
|
config: Dict,
|
||||||
|
@ -101,20 +99,20 @@ class Wallet:
|
||||||
self.server = server
|
self.server = server
|
||||||
|
|
||||||
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None):
|
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None):
|
||||||
ret = []
|
condition_list = []
|
||||||
if primaries:
|
if primaries:
|
||||||
for primary in primaries:
|
for primary in primaries:
|
||||||
ret.append(
|
condition_list.append(
|
||||||
make_create_coin_condition(primary["puzzlehash"], primary["amount"])
|
make_create_coin_condition(primary["puzzlehash"], primary["amount"])
|
||||||
)
|
)
|
||||||
if consumed:
|
if consumed:
|
||||||
for coin in 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:
|
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:
|
if me:
|
||||||
ret.append(make_assert_my_coin_id_condition(me["id"]))
|
condition_list.append(make_assert_my_coin_id_condition(me["id"]))
|
||||||
return clvm.to_sexp_f([puzzle_for_conditions(ret), []])
|
return clvm.to_sexp_f([puzzle_for_conditions(condition_list), []])
|
||||||
|
|
||||||
async def get_keys(
|
async def get_keys(
|
||||||
self, hash: bytes32
|
self, hash: bytes32
|
||||||
|
@ -131,20 +129,33 @@ class Wallet:
|
||||||
async def generate_unsigned_transaction(
|
async def generate_unsigned_transaction(
|
||||||
self, amount: int, newpuzzlehash: bytes32, fee: int = 0
|
self, amount: int, newpuzzlehash: bytes32, fee: int = 0
|
||||||
) -> List[Tuple[Program, CoinSolution]]:
|
) -> 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:
|
if utxos is None:
|
||||||
return []
|
return []
|
||||||
spends: List[Tuple[Program, CoinSolution]] = []
|
|
||||||
output_created = False
|
|
||||||
spend_value = sum([coin.amount for coin in utxos])
|
spend_value = sum([coin.amount for coin in utxos])
|
||||||
change = spend_value - amount - fee
|
change = spend_value - amount - fee
|
||||||
|
|
||||||
|
spends: List[Tuple[Program, CoinSolution]] = []
|
||||||
|
output_created = False
|
||||||
|
|
||||||
for coin in utxos:
|
for coin in utxos:
|
||||||
|
# Get keys for puzzle_hash
|
||||||
puzzle_hash = coin.puzzle_hash
|
puzzle_hash = coin.puzzle_hash
|
||||||
maybe = await self.get_keys(puzzle_hash)
|
maybe = await self.get_keys(puzzle_hash)
|
||||||
if not maybe:
|
if not maybe:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# Get puzzle for pubkey
|
||||||
pubkey, secretkey = maybe
|
pubkey, secretkey = maybe
|
||||||
puzzle: Program = puzzle_for_pk(pubkey.serialize())
|
puzzle: Program = puzzle_for_pk(pubkey.serialize())
|
||||||
|
|
||||||
|
# Only one coin creates outputs
|
||||||
if output_created is False:
|
if output_created is False:
|
||||||
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
|
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
|
||||||
if change > 0:
|
if change > 0:
|
||||||
|
@ -154,29 +165,38 @@ class Wallet:
|
||||||
solution = self.make_solution(primaries=primaries)
|
solution = self.make_solution(primaries=primaries)
|
||||||
output_created = True
|
output_created = True
|
||||||
else:
|
else:
|
||||||
|
# TODO coin consumed condition should be removed
|
||||||
solution = self.make_solution(consumed=[coin.name()])
|
solution = self.make_solution(consumed=[coin.name()])
|
||||||
|
|
||||||
spends.append((puzzle, CoinSolution(coin, solution)))
|
spends.append((puzzle, CoinSolution(coin, solution)))
|
||||||
return spends
|
return spends
|
||||||
|
|
||||||
async def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
|
async def sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]):
|
||||||
sigs = []
|
signatures = []
|
||||||
for puzzle, solution in spends:
|
for puzzle, solution in spends:
|
||||||
|
# Get keys
|
||||||
keys = await self.get_keys(solution.coin.puzzle_hash)
|
keys = await self.get_keys(solution.coin.puzzle_hash)
|
||||||
if not keys:
|
if not keys:
|
||||||
return None
|
return None
|
||||||
pubkey, secretkey = keys
|
pubkey, secretkey = keys
|
||||||
secretkey = BLSPrivateKey(secretkey)
|
secretkey = BLSPrivateKey(secretkey)
|
||||||
|
|
||||||
code_ = [puzzle, solution.solution]
|
code_ = [puzzle, solution.solution]
|
||||||
sexp = clvm.to_sexp_f(code_)
|
sexp = clvm.to_sexp_f(code_)
|
||||||
|
|
||||||
|
# Get AGGSIG conditions
|
||||||
err, con, cost = conditions_for_solution(sexp)
|
err, con, cost = conditions_for_solution(sexp)
|
||||||
if err or not con:
|
if err or not con:
|
||||||
return None
|
return None
|
||||||
conditions_dict = conditions_by_opcode(con)
|
conditions_dict = conditions_by_opcode(con)
|
||||||
|
|
||||||
for _ in hash_key_pairs_for_conditions_dict(conditions_dict):
|
# Create signature
|
||||||
signature = secretkey.sign(_.message_hash)
|
for pk_message in hash_key_pairs_for_conditions_dict(conditions_dict):
|
||||||
sigs.append(signature)
|
signature = secretkey.sign(pk_message.message_hash)
|
||||||
aggsig = BLSSignature.aggregate(sigs)
|
signatures.append(signature)
|
||||||
|
|
||||||
|
# Aggregate signatures
|
||||||
|
aggsig = BLSSignature.aggregate(signatures)
|
||||||
solution_list: List[CoinSolution] = [
|
solution_list: List[CoinSolution] = [
|
||||||
CoinSolution(
|
CoinSolution(
|
||||||
coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])
|
coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])
|
||||||
|
@ -184,6 +204,7 @@ class Wallet:
|
||||||
for (puzzle, coin_solution) in spends
|
for (puzzle, coin_solution) in spends
|
||||||
]
|
]
|
||||||
spend_bundle = SpendBundle(solution_list, aggsig)
|
spend_bundle = SpendBundle(solution_list, aggsig)
|
||||||
|
|
||||||
return spend_bundle
|
return spend_bundle
|
||||||
|
|
||||||
async def generate_signed_transaction(
|
async def generate_signed_transaction(
|
||||||
|
|
|
@ -11,6 +11,7 @@ from src.util.ints import uint32
|
||||||
from src.util.api_decorators import api_request
|
from src.util.api_decorators import api_request
|
||||||
from src.wallet.wallet import Wallet
|
from src.wallet.wallet import Wallet
|
||||||
from src.wallet.wallet_state_manager import WalletStateManager
|
from src.wallet.wallet_state_manager import WalletStateManager
|
||||||
|
from src.wallet.block_record import BlockRecord
|
||||||
|
|
||||||
|
|
||||||
class WalletNode:
|
class WalletNode:
|
||||||
|
@ -22,6 +23,7 @@ class WalletNode:
|
||||||
log: logging.Logger
|
log: logging.Logger
|
||||||
wallet: Wallet
|
wallet: Wallet
|
||||||
constants: Dict
|
constants: Dict
|
||||||
|
short_sync_threshold: int
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def create(
|
async def create(
|
||||||
|
@ -49,6 +51,7 @@ class WalletNode:
|
||||||
self.wallet = await Wallet.create(config, key_config, self.wallet_state_manager)
|
self.wallet = await Wallet.create(config, key_config, self.wallet_state_manager)
|
||||||
|
|
||||||
self.server = None
|
self.server = None
|
||||||
|
self.short_sync_threshold = 10
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ class WalletNode:
|
||||||
if request.weight < lca.weight:
|
if request.weight < lca.weight:
|
||||||
return
|
return
|
||||||
|
|
||||||
if int(request.height) - int(lca.height) > 10:
|
if int(request.height) - int(lca.height) > self.short_sync_threshold:
|
||||||
try:
|
try:
|
||||||
# Performs sync, and catch exceptions so we don't close the connection
|
# Performs sync, and catch exceptions so we don't close the connection
|
||||||
async for ret_msg in self._sync():
|
async for ret_msg in self._sync():
|
||||||
|
@ -126,15 +129,60 @@ class WalletNode:
|
||||||
|
|
||||||
@api_request
|
@api_request
|
||||||
async def respond_header(self, response: wallet_protocol.RespondHeader):
|
async def respond_header(self, response: wallet_protocol.RespondHeader):
|
||||||
# TODO(mariano): implement
|
block = response.header_block
|
||||||
# 0. If we already have, return
|
# 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
|
# 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
|
# 2. If we have transactions, fetch adds/deletes
|
||||||
# adds_deletes = await self.wallet_state_manager.filter_additions_removals()
|
if response.transactions_filter is not None:
|
||||||
# 3. If we don't have, don't fetch
|
(
|
||||||
# 4. If we have the next header cached, process it
|
additions,
|
||||||
pass
|
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
|
@api_request
|
||||||
async def reject_header_request(
|
async def reject_header_request(
|
||||||
|
|
|
@ -365,18 +365,18 @@ class WalletStateManager:
|
||||||
] = await self.wallet_store.get_coin_records_by_spent(False)
|
] = await self.wallet_store.get_coin_records_by_spent(False)
|
||||||
my_puzzle_hashes = await self.puzzle_store.get_all_puzzle_hashes()
|
my_puzzle_hashes = await self.puzzle_store.get_all_puzzle_hashes()
|
||||||
|
|
||||||
removals_of_interests: bytes32 = []
|
removals_of_interest: bytes32 = []
|
||||||
additions_of_interest: bytes32 = []
|
additions_of_interest: bytes32 = []
|
||||||
|
|
||||||
for record in my_coin_records:
|
for record in my_coin_records:
|
||||||
if tx_filter.Match(bytearray(record.name())):
|
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:
|
for puzzle_hash in my_puzzle_hashes:
|
||||||
if tx_filter.Match(bytearray(puzzle_hash)):
|
if tx_filter.Match(bytearray(puzzle_hash)):
|
||||||
additions_of_interest.append(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]:
|
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) """
|
""" 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
|
Rolls back and updates the coin_store and transaction store. It's possible this height
|
||||||
is the tip, or even beyond the tip.
|
is the tip, or even beyond the tip.
|
||||||
"""
|
"""
|
||||||
print("Doing reorg...")
|
|
||||||
await self.wallet_store.rollback_lca_to_block(index)
|
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]):
|
async def retry_sending_after_reorg(self, records: List[TransactionRecord]):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -227,3 +227,26 @@ class WalletTransactionStore:
|
||||||
records.append(record)
|
records.append(record)
|
||||||
|
|
||||||
return records
|
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()
|
||||||
|
|
Loading…
Reference in New Issue