Update FAQ & Community node list

This commit is contained in:
hinto-janaiyo 2022-12-14 21:09:37 -05:00
parent 36ba38f1d9
commit 5e2f0b7ed7
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
5 changed files with 103 additions and 78 deletions

37
Cargo.lock generated
View file

@ -63,9 +63,9 @@ checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6"
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.17.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@ -343,15 +343,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.66" version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
"miniz_oxide 0.5.4", "miniz_oxide",
"object", "object",
"rustc-demangle", "rustc-demangle",
] ]
@ -1322,7 +1322,7 @@ dependencies = [
"flume", "flume",
"half", "half",
"lebe", "lebe",
"miniz_oxide 0.6.2", "miniz_oxide",
"smallvec", "smallvec",
"threadpool", "threadpool",
] ]
@ -1391,7 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide 0.6.2", "miniz_oxide",
] ]
[[package]] [[package]]
@ -1695,9 +1695,9 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.26.2" version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
[[package]] [[package]]
name = "gio-sys" name = "gio-sys"
@ -2299,15 +2299,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [
"adler",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.6.2" version = "0.6.2"
@ -2656,9 +2647,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.29.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2957,7 +2948,7 @@ dependencies = [
"bitflags", "bitflags",
"crc32fast", "crc32fast",
"flate2", "flate2",
"miniz_oxide 0.6.2", "miniz_oxide",
] ]
[[package]] [[package]]
@ -4130,9 +4121,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",

View file

@ -9,13 +9,14 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
* [How-To](#How-To) * [How-To](#How-To)
- [Video](#Video) - [Video](#Video)
- [Text](#Text) - [Text](#Text)
- [Simple](#Simple) * [Simple](#Simple)
- [Gupax](#Gupax) - [Gupax](#Gupax)
- [P2Pool](#P2Pool) - [P2Pool](#P2Pool)
- [XMRig](#XMRig) - [XMRig](#XMRig)
- [Advanced](#Advanced) * [Advanced](#Advanced)
- [Verifying](#Verifying) - [Verifying](#Verifying)
- [Command Line](#Command-Line) - [Command Line](#Command-Line)
- [Resolution](#Resolution)
- [Tor/Arti](#TorArti) - [Tor/Arti](#TorArti)
- [Logs](#Logs) - [Logs](#Logs)
- [Disk](#Disk) - [Disk](#Disk)
@ -23,7 +24,7 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
- [Gupax](#Gupax) - [Gupax](#Gupax)
- [P2Pool](#P2Pool) - [P2Pool](#P2Pool)
- [XMRig](#XMRig) - [XMRig](#XMRig)
- [Connections](#Connections) * [Connections](#Connections)
* [Community Monero Nodes](#community-monero-nodes) * [Community Monero Nodes](#community-monero-nodes)
* [Build](#Build) * [Build](#Build)
- [General Info](#General-Info) - [General Info](#General-Info)
@ -31,6 +32,11 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
- [macOS](#macOS) - [macOS](#macOS)
- [Windows](#Windows) - [Windows](#Windows)
* [FAQ](#FAQ) * [FAQ](#FAQ)
- [Where are updates downloaded from?](#where-are-updates-downloaded-from)
- [Can I quit mid-update?](#can-i-quit-mid-update)
- [How much memory does Gupax use?](#how-much-memory-does-gupax-use)
- [How is sudo handled? (on macOS/Linux)](#how-is-sudo-handled-on-macos-linux)
- [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows)
## What is Monero/P2Pool/XMRig/Gupax? ## What is Monero/P2Pool/XMRig/Gupax?
**Monero** is a secure, private, and untraceable cryptocurrency. **Monero** is a secure, private, and untraceable cryptocurrency.
@ -133,28 +139,25 @@ For transparency, here's all the connections Gupax makes:
| Community Monero Nodes | Connecting to with P2Pool, measuring ping latency | `[P2Pool Simple]` tab | [`node.rs`](https://github.com/hinto-janaiyo/gupax/blob/main/src/node.rs) | | Community Monero Nodes | Connecting to with P2Pool, measuring ping latency | `[P2Pool Simple]` tab | [`node.rs`](https://github.com/hinto-janaiyo/gupax/blob/main/src/node.rs) |
## Community Monero Nodes ## Community Monero Nodes
| Name | IP/Domain | RPC Port | | Name | IP/Domain | RPC Port | ZMQ Port |
|----------------|----------------------------------|----------| |-------------------------------------------------------|----------------------------------|----------|----------|
| C3pool | node.c3pool.com | 18081 | | [C3pool](https://www.c3pool.com) | node.c3pool.com | 18081 | 18083 |
| Cake | xmr-node.cakewallet.com | 18081 | | [Cake](https://cakewallet.com) | xmr-node.cakewallet.com | 18081 | 18083 |
| CakeEu | xmr-node-eu.cakewallet.com | 18081 | | [CakeEu](https://cakewallet.com) | xmr-node-eu.cakewallet.com | 18081 | 18083 |
| CakeUk | xmr-node-uk.cakewallet.com | 18081 | | [CakeUk](https://cakewallet.com) | xmr-node-uk.cakewallet.com | 18081 | 18083 |
| CakeUs | xmr-node-usa-east.cakewallet.com | 18081 | | [CakeUs](https://cakewallet.com) | xmr-node-usa-east.cakewallet.com | 18081 | 18083 |
| Feather1 | selsta1.featherwallet.net | 18081 | | [Feather1](https://github.com/feather-wallet/feather) | selsta1.featherwallet.net | 18081 | 18083 |
| Feather2 | selsta2.featherwallet.net | 18081 | | [Feather2](https://github.com/feather-wallet/feather) | selsta2.featherwallet.net | 18081 | 18083 |
| MajesticBankIs | node.majesticbank.is | 18089 | | [MajesticBankIs](https://www.majesticbank.sc) | node.majesticbank.is | 18089 | 18083 |
| MajesticBankSu | node.majesticbank.su | 18089 | | [MajesticBankSu](https://www.majesticbank.sc) | node.majesticbank.su | 18089 | 18083 |
| Monerujo | nodex.monerujo.io | 18081 | | [Monerujo](https://www.monerujo.io) | nodex.monerujo.io | 18081 | 18083 |
| Plowsof1 | node.monerodevs.org | 18089 | | [Plowsof1](https://github.com/plowsof) | node.monerodevs.org | 18089 | 18084 |
| Plowsof2 | node2.monerodevs.org | 18089 | | [Plowsof2](https://github.com/plowsof) | node2.monerodevs.org | 18089 | 18084 |
| Rino | node.community.rino.io | 18081 | | [Rino](https://cakewallet.com) | node.community.rino.io | 18081 | 18083 |
| Seth | node.sethforprivacy.com | 18089 | | [Seth](https://github.com/sethforprivacy) | node.sethforprivacy.com | 18089 | 18083 |
| Singapore | singapore.node.xmr | 18081 | | [SupportXmr](https://www.supportxmr.com) | node.supportxmr.com | 18081 | 18083 |
| SupportXmr | node.supportxmr.com | 18081 | | [SupportXmrIr](https://www.supportxmr.com) | node.supportxmr.ir | 18089 | 18083 |
| SupportXmrIr | node.supportxmr.ir | 18089 | | [XmrVsBeast](https://xmrvsbeast.com) | p2pmd.xmrvsbeast.com | 18081 | 18083 |
| XmrVsBeast | p2pmd.xmrvsbeast.com | 18081 |
*Note: Plowsof1 & Plowsof2 have ZMQ port on 18084, the rest are 18083*
## Build ## Build
### General Info ### General Info
@ -200,3 +203,36 @@ cargo build --release
This will build Gupax with the MSVC toolchain (`x86_64-pc-windows-msvc`). This is the recommended method and is how the pre-compiled release binaries are built. This will build Gupax with the MSVC toolchain (`x86_64-pc-windows-msvc`). This is the recommended method and is how the pre-compiled release binaries are built.
## FAQ ## FAQ
### Where are updates downloaded from?
The latest versions are downloaded using the GitHub API.
* Gupax [`https://github.com/hinto-janaiyo/gupax`](https://github.com/hinto-janaiyo/gupax)
* P2Pool [`https://github.com/SChernykh/p2pool`](https://github.com/SChernykh/p2pool)
* XMRig [`https://github.com/xmrig/xmrig`](https://github.com/xmrig/xmrig)
GitHub's API blocks request that do not have an HTTP `User-Agent` header. [For privacy, Gupax randomly uses a recent version of a `Wget/Curl` user-agent.](https://github.com/hinto-janaiyo/gupax/blob/2b80aa027728ddd193bac2e77caa5ddb4323f8fd/src/update.rs#L134)
---
### Can I quit mid-update?
Although Gupax uses a temporary folder (`gupax_update_[A-Za-z0-9]`) to store temporary downloaded files, there aren't measures in place to revert an upgrade once the file swapping has actually started. If you quit Gupax anytime before the `Upgrading packages` phase (after metadata, download, extraction), you will technically be safe but this is not recommended as it is risky, especially since these updates can be very fast.
If you started an update, you should let it finish. If the update has been stuck for a *long* time, it may be worth quitting Gupax. The worst that can happen is that your `Gupax/P2Pool/XMRig` binaries may be moved/deleted. Those can be easily redownloaded. Your actual `Gupax` user data (settings, custom nodes, pools, etc) is never touched.
---
### How much memory does Gupax use?
Gupax itself uses around 100-300 megabytes of memory.
Gupax also holds up to [500,000 bytes](https://github.com/hinto-janaiyo/gupax/blob/2b80aa027728ddd193bac2e77caa5ddb4323f8fd/src/helper.rs#L63) of log data from `P2Pool/XMRig` to display in the GUI terminals. These logs are reset once over capacity which takes around 1-2 hours.
Memory usage should *never* be above 400~ megabytes. If you see Gupax using more than this, please send a bug report.
---
### How is sudo handled? (on macOS/Linux)
[See here for more info.](https://github.com/hinto-janaiyo/gupax/tree/main/src#sudo)
---
### Why does Gupax need to be Admin? (on Windows)
[See here for more info.](https://github.com/hinto-janaiyo/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows)

View file

@ -5,6 +5,7 @@
* [Disk](#Disk) * [Disk](#Disk)
* [Scale](#Scale) * [Scale](#Scale)
* [Naming Scheme](#naming-scheme) * [Naming Scheme](#naming-scheme)
* [Sudo](#Sudo)
* [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows) * [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows)
- [The issue](#the-issue) - [The issue](#the-issue)
- [The requirements](#the-requirements) - [The requirements](#the-requirements)
@ -120,6 +121,13 @@ Exceptions (there are always exceptions...):
- XMRig separates the hash and signature - XMRig separates the hash and signature
- P2Pool hashes are in UPPERCASE - P2Pool hashes are in UPPERCASE
## Sudo
Unlike Windows, Unix (macOS/Linux) have a userland program that handles all the dirty details of privilege escalation: `sudo`.
`sudo` is used in Gupax to execute XMRig, to enable MSR mods and hugepages. After every use of `sudo`, the memory holding the `String` buffer containing the password is wiped with 0's using [`zeroize`](https://docs.rs/zeroize/) to make sure the compiler doesn't optimize away the wipe. Although memory *should* be safe, this prevents passive accidents (core-dumps revealing plain-text password) and active attacks (attackers accessing live process memory) from happening.
In general, secrets should be ephemeral or encrypted if not in use. I considered using [`secrets`](https://docs.rs/secrets/) to keep the password encrypted so that the user would only have to enter their password once, but simply wiping the memory was easier to implement and caused less worries of handling things incorrectly.
## Why does Gupax need to be Admin? (on Windows) ## Why does Gupax need to be Admin? (on Windows)
**TL;DR:** Because Windows. **TL;DR:** Because Windows.

View file

@ -265,27 +265,16 @@ impl Helper {
// Reset output if larger than max bytes. // Reset output if larger than max bytes.
// This will also append a message showing it was reset. // This will also append a message showing it was reset.
fn check_reset_gui_p2pool_output(gui_api: &Arc<Mutex<PubP2poolApi>>) { fn check_reset_gui_output(output: &mut String, name: ProcessName) {
debug!("P2Pool Watchdog | Checking if GUI output needs reset..."); let len = output.len();
let mut gui_api = gui_api.lock().unwrap(); if len > GUI_OUTPUT_LEEWAY {
if gui_api.output.len() > GUI_OUTPUT_LEEWAY { info!("{} Watchdog | Output is nearing {} bytes, resetting!", name, MAX_GUI_OUTPUT_BYTES);
info!("P2Pool | Output is nearing {} bytes, resetting!", MAX_GUI_OUTPUT_BYTES); let text = format!("{}\n{} GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you!\n{}\n\n\n\n", name, HORI_CONSOLE, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
let text = format!("{}\nP2Pool GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you!\n{}\n\n\n\n", HORI_CONSOLE, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE); output.clear();
gui_api.output.clear(); output.push_str(&text);
gui_api.output.push_str(&text); debug!("{} Watchdog | Resetting GUI output ... OK", name);
debug!("P2Pool Watchdog | Resetting GUI output ... OK"); } else {
} debug!("{} Watchdog | GUI output reset not needed! Current byte length ... {}", name, len);
}
fn check_reset_gui_xmrig_output(gui_api: &Arc<Mutex<PubXmrigApi>>) {
debug!("XMRig Watchdog | Checking if GUI output needs reset...");
let mut gui_api = gui_api.lock().unwrap();
if gui_api.output.len() > GUI_OUTPUT_LEEWAY {
info!("XMRig | Output is nearing {} bytes, resetting!", MAX_GUI_OUTPUT_BYTES);
let text = format!("{}\nXMRig GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you!\n{}\n\n\n\n", HORI_CONSOLE, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
gui_api.output.clear();
gui_api.output.push_str(&text);
debug!("XMRig Watchdog | Resetting GUI output ... OK");
} }
} }
@ -478,7 +467,7 @@ impl Helper {
use std::io::Write; use std::io::Write;
if std::fs::File::create(&api_path).is_ok() { if std::fs::File::create(&api_path).is_ok() {
let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#; let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#;
match std::fs::write(&path, text) { match std::fs::write(&api_path, text) {
Ok(_) => info!("P2Pool | Creating default empty API file ... OK"), Ok(_) => info!("P2Pool | Creating default empty API file ... OK"),
Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e), Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e),
} }
@ -565,7 +554,9 @@ impl Helper {
// Check if logs need resetting // Check if logs need resetting
debug!("P2Pool Watchdog | Attempting GUI log reset check"); debug!("P2Pool Watchdog | Attempting GUI log reset check");
Self::check_reset_gui_p2pool_output(&gui_api); let mut lock = gui_api.lock().unwrap();
Self::check_reset_gui_output(&mut lock.output, ProcessName::P2pool);
drop(lock);
// Always update from output // Always update from output
debug!("P2Pool Watchdog | Starting [update_from_output()]"); debug!("P2Pool Watchdog | Starting [update_from_output()]");
@ -905,7 +896,9 @@ impl Helper {
// Check if logs need resetting // Check if logs need resetting
debug!("XMRig Watchdog | Attempting GUI log reset check"); debug!("XMRig Watchdog | Attempting GUI log reset check");
Self::check_reset_gui_xmrig_output(&gui_api); let mut lock = gui_api.lock().unwrap();
Self::check_reset_gui_output(&mut lock.output, ProcessName::Xmrig);
drop(lock);
// Always update from output // Always update from output
debug!("XMRig Watchdog | Starting [update_from_output()]"); debug!("XMRig Watchdog | Starting [update_from_output()]");

View file

@ -49,18 +49,17 @@ pub const RINO: &str = "node.community.rino.io:18081";
pub const SETH: &str = "node.sethforprivacy.com:18089"; pub const SETH: &str = "node.sethforprivacy.com:18089";
pub const SUPPORTXMR: &str = "node.supportxmr.com:18081"; pub const SUPPORTXMR: &str = "node.supportxmr.com:18081";
pub const SUPPORTXMR_IR: &str = "node.supportxmr.ir:18081"; pub const SUPPORTXMR_IR: &str = "node.supportxmr.ir:18081";
pub const SINGAPORE: &str = "singapore.node.xmr.pm:18089";
pub const XMRVSBEAST: &str = "p2pmd.xmrvsbeast.com:18081"; pub const XMRVSBEAST: &str = "p2pmd.xmrvsbeast.com:18081";
pub const NODE_IPS: [&str; 18] = [ pub const NODE_IPS: [&str; 17] = [
C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,FEATHER_1,FEATHER_2,MAJESTICBANK_IS,MAJESTICBANK_SU, C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,FEATHER_1,FEATHER_2,MAJESTICBANK_IS,MAJESTICBANK_SU,
MONERUJO,PLOWSOF_1,PLOWSOF_2,RINO,SETH,SUPPORTXMR,SUPPORTXMR_IR,SINGAPORE,XMRVSBEAST, MONERUJO,PLOWSOF_1,PLOWSOF_2,RINO,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
]; ];
#[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] #[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum NodeEnum { pub enum NodeEnum {
C3pool,Cake,CakeEu,CakeUk,CakeUs,MajesticBankIs,MajesticBankSu,Monerujo,Plowsof1, C3pool,Cake,CakeEu,CakeUk,CakeUs,MajesticBankIs,MajesticBankSu,Monerujo,Plowsof1,
Plowsof2,Rino,Feather1,Feather2,Seth,SupportXmr,SupportXmrIr,Singapore,XmrVsBeast, Plowsof2,Rino,Feather1,Feather2,Seth,SupportXmr,SupportXmrIr,XmrVsBeast,
} }
impl std::fmt::Display for NodeEnum { impl std::fmt::Display for NodeEnum {
@ -112,7 +111,6 @@ pub fn ip_to_enum(ip: &'static str) -> NodeEnum {
PLOWSOF_2 => Plowsof2, PLOWSOF_2 => Plowsof2,
RINO => Rino, RINO => Rino,
SETH => Seth, SETH => Seth,
SINGAPORE => Singapore,
SUPPORTXMR => SupportXmr, SUPPORTXMR => SupportXmr,
SUPPORTXMR_IR => SupportXmrIr, SUPPORTXMR_IR => SupportXmrIr,
_ => XmrVsBeast, _ => XmrVsBeast,
@ -135,7 +133,6 @@ pub fn enum_to_ip(node: NodeEnum) -> &'static str {
Plowsof2 => PLOWSOF_2, Plowsof2 => PLOWSOF_2,
Rino => RINO, Rino => RINO,
Seth => SETH, Seth => SETH,
Singapore => SINGAPORE,
SupportXmr => SUPPORTXMR, SupportXmr => SUPPORTXMR,
SupportXmrIr => SUPPORTXMR_IR, SupportXmrIr => SUPPORTXMR_IR,
_ => XMRVSBEAST _ => XMRVSBEAST