Rework token lists and ID generation.

This commit is contained in:
parazyd
2022-04-30 19:44:32 +02:00
parent c11bba2cee
commit 609afbd9c7
18 changed files with 275 additions and 479 deletions

2
Cargo.lock generated
View File

@@ -1261,6 +1261,7 @@ dependencies = [
"async-std",
"async-trait",
"blake3",
"bs58",
"chrono",
"ctrlc-async",
"darkfi",
@@ -1270,6 +1271,7 @@ dependencies = [
"lazy-init",
"log",
"num-bigint",
"pasta_curves",
"rand",
"serde",
"serde_derive",

View File

@@ -14,6 +14,7 @@ async-executor = "1.4.1"
async-std = "1.11.0"
async-trait = "0.1.53"
blake3 = "1.3.1"
bs58 = "0.4.0"
chrono = "0.4.19"
ctrlc-async = {version = "3.2.2", default-features = false, features = ["async-std", "termination"]}
darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net", "node"]}
@@ -23,6 +24,7 @@ fxhash = "0.2.1"
lazy-init = "0.5.0"
log = "0.4.16"
num-bigint = {version = "0.4.3", features = ["serde"]}
pasta_curves = "0.3.1"
rand = "0.8.5"
serde_json = "1.0.79"
simplelog = "0.12.0"

View File

@@ -24,11 +24,7 @@ use darkfi::{
util::Timestamp,
ValidatorState, MAINNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_HASH_BYTES,
},
crypto::{
address::Address,
keypair::PublicKey,
token_list::{DrkTokenList, TokenList},
},
crypto::{address::Address, keypair::PublicKey, token_list::DrkTokenList},
net,
net::P2pPtr,
node::Client,
@@ -150,10 +146,7 @@ pub struct Darkfid {
sync_p2p: Option<P2pPtr>,
client: Arc<Client>,
validator_state: ValidatorStatePtr,
drk_tokenlist: DrkTokenList,
btc_tokenlist: TokenList,
eth_tokenlist: TokenList,
sol_tokenlist: TokenList,
tokenlist: DrkTokenList,
}
// JSON-RPC methods
@@ -193,17 +186,8 @@ impl Darkfid {
validator_state: ValidatorStatePtr,
consensus_p2p: Option<P2pPtr>,
sync_p2p: Option<P2pPtr>,
tokenlist: DrkTokenList,
) -> Result<Self> {
debug!("Parsing token lists...");
let btc_tokenlist =
TokenList::new(include_bytes!("../../../contrib/token/bitcoin_token_list.min.json"))?;
let eth_tokenlist =
TokenList::new(include_bytes!("../../../contrib/token/erc20_token_list.min.json"))?;
let sol_tokenlist =
TokenList::new(include_bytes!("../../../contrib/token/solana_token_list.min.json"))?;
debug!("Creating drk tokenlist");
let drk_tokenlist = DrkTokenList::new(&sol_tokenlist, &eth_tokenlist, &btc_tokenlist)?;
debug!("Waiting for validator state lock");
let client = validator_state.read().await.client.clone();
debug!("Released validator state lock");
@@ -214,10 +198,7 @@ impl Darkfid {
sync_p2p,
client,
validator_state,
drk_tokenlist,
btc_tokenlist,
eth_tokenlist,
sol_tokenlist,
tokenlist,
})
}
}
@@ -283,6 +264,15 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
)
.await?;
debug!("Parsing token lists...");
let tokenlist = DrkTokenList::new(&[
("drk", include_bytes!("../../../contrib/token/darkfi_token_list.min.json")),
("btc", include_bytes!("../../../contrib/token/bitcoin_token_list.min.json")),
("eth", include_bytes!("../../../contrib/token/erc20_token_list.min.json")),
("sol", include_bytes!("../../../contrib/token/solana_token_list.min.json")),
])?;
debug!("Finished parsing token lists");
let sync_p2p = {
info!("Registering block sync P2P protocols...");
let sync_network_settings = net::Settings {
@@ -298,10 +288,16 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
let registry = p2p.protocol_registry();
let _state = state.clone();
let _tokenlist = tokenlist.clone();
registry
.register(net::SESSION_ALL, move |channel, p2p| {
let state = _state.clone();
async move { ProtocolSync::init(channel, state, p2p, args.consensus).await.unwrap() }
let tokenlist = _tokenlist.clone();
async move {
ProtocolSync::init(channel, state, tokenlist, p2p, args.consensus)
.await
.unwrap()
}
})
.await;
@@ -376,7 +372,9 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
};
// Initialize program state
let darkfid = Darkfid::new(state.clone(), consensus_p2p.clone(), sync_p2p.clone()).await?;
let darkfid =
Darkfid::new(state.clone(), consensus_p2p.clone(), sync_p2p.clone(), tokenlist.clone())
.await?;
let darkfid = Arc::new(darkfid);
// JSON-RPC server
@@ -394,7 +392,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
})
.detach();
match block_sync_task(sync_p2p.clone().unwrap(), state.clone()).await {
match block_sync_task(sync_p2p.clone().unwrap(), state.clone(), &tokenlist).await {
Ok(()) => *darkfid.synced.lock().await = true,
Err(e) => error!("Failed syncing blockchain: {}", e),
}

View File

@@ -5,14 +5,11 @@ use serde_json::{json, Value};
use darkfi::{
consensus::Tx,
crypto::{address::Address, keypair::PublicKey},
crypto::{address::Address, keypair::PublicKey, token_id::generate_id},
rpc::{
jsonrpc,
jsonrpc::{
ErrorCode::{
InternalError, InvalidAddressParam, InvalidAmountParam, InvalidParams,
InvalidTokenIdParam,
},
ErrorCode::{InternalError, InvalidAddressParam, InvalidAmountParam, InvalidParams},
JsonResult,
},
},
@@ -88,12 +85,17 @@ impl Darkfid {
}
};
let token_id = if let Some(token_id) =
self.drk_tokenlist.tokens[&network].get(&token.to_uppercase())
let token_id = if let Some(tok) = self.tokenlist.by_net[&network].get(token.to_uppercase())
{
token_id
tok.drk_address
} else {
return jsonrpc::error(InvalidTokenIdParam, None, id).into()
match generate_id(&network, &token) {
Ok(v) => v,
Err(e) => {
error!("transfer(): Failed generate_id(): {}", e);
return jsonrpc::error(InternalError, None, id).into()
}
}
};
let tx = match self
@@ -101,7 +103,7 @@ impl Darkfid {
.build_transaction(
pubkey,
amount,
*token_id,
token_id,
false,
self.validator_state.read().await.state_machine.clone(),
)

View File

@@ -1,6 +1,7 @@
use fxhash::FxHashMap;
use log::error;
use log::{error, warn};
use num_bigint::BigUint;
use pasta_curves::group::ff::PrimeField;
use serde_json::{json, Value};
use darkfi::{
@@ -15,8 +16,7 @@ use darkfi::{
JsonResult,
},
},
util::{decode_base10, encode_base10},
Result,
util::{decode_base10, encode_base10, NetworkName},
};
use super::Darkfid;
@@ -199,45 +199,60 @@ impl Darkfid {
// --> {"jsonrpc": "2.0", "method": "wallet.get_balances", "params": [], "id": 1}
// <-- {"jsonrpc": "2.0", "result": [{"btc": [100, "Bitcoin"]}, {...}], "id": 1}
pub async fn get_balances(&self, id: Value, _params: &[Value]) -> JsonResult {
let result: Result<FxHashMap<String, (String, String)>> = async {
let balances = self.client.get_balances().await?;
let mut symbols: FxHashMap<String, (String, String)> = FxHashMap::default();
for b in balances.list.iter() {
let network: String;
let symbol: String;
let mut amount = BigUint::from(b.value);
if let Some((net, sym)) = self.drk_tokenlist.symbol_from_id(&b.token_id)? {
network = net.to_string();
symbol = sym;
} else {
// TODO: SQL needs to have the mint address for show, not the internal hash.
// TODO: SQL needs to have the network name
network = String::from("UNKNOWN");
symbol = format!("{:?}", b.token_id);
}
if let Some(prev) = symbols.get(&symbol) {
let prev_amnt = decode_base10(&prev.0, 8, true)?;
amount += prev_amnt;
}
let amount = encode_base10(amount, 8);
symbols.insert(symbol, (amount, network));
}
Ok(symbols)
}
.await;
match result {
Ok(res) => jsonrpc::response(json!(res), id).into(),
let balances = match self.client.get_balances().await {
Ok(v) => v,
Err(e) => {
error!("Failed fetching balances from wallet: {}", e);
jsonrpc::error(InternalError, None, id).into()
return jsonrpc::error(InternalError, None, id).into()
}
};
// k: ticker/drk_addr, v: (amount, network, net_addr, drk_addr)
let mut ret: FxHashMap<String, (String, String, String, String)> = FxHashMap::default();
for balance in balances.list {
let drk_addr = bs58::encode(balance.token_id.to_repr()).into_string();
let mut amount = BigUint::from(balance.value);
let (net_name, net_addr) =
if let Some((net, tok)) = self.tokenlist.by_addr.get(&drk_addr) {
(net, tok.net_address.clone())
} else {
warn!("Could not find network name and token info for {}", drk_addr);
(&NetworkName::DarkFi, "unknown".to_string())
};
let mut ticker = None;
for (k, v) in self.tokenlist.by_net[net_name].0.iter() {
if v.net_address == net_addr {
ticker = Some(k.clone());
break
}
}
if ticker.is_none() {
ticker = Some(drk_addr.clone())
}
let ticker = ticker.unwrap();
if let Some(prev) = ret.get(&ticker) {
// TODO: We shouldn't be hardcoding everything to 8 decimals.
let prev_amnt = match decode_base10(&prev.0, 8, false) {
Ok(v) => v,
Err(e) => {
error!("Failed to decode_base10(): {}", e);
return jsonrpc::error(InternalError, None, id).into()
}
};
amount += prev_amnt;
}
let amount = encode_base10(amount, 8);
ret.insert(ticker, (amount, net_name.to_string(), net_addr, drk_addr));
}
jsonrpc::response(json!(ret), id).into()
}
}

View File

@@ -23,7 +23,7 @@ use darkfi::{
Timestamp, Tx, ValidatorState, ValidatorStatePtr, MAINNET_GENESIS_HASH_BYTES,
TESTNET_GENESIS_HASH_BYTES,
},
crypto::{address::Address, keypair::PublicKey, types::DrkTokenId},
crypto::{address::Address, keypair::PublicKey, token_list::DrkTokenList, types::DrkTokenId},
net,
net::P2pPtr,
node::Client,
@@ -358,6 +358,13 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
)
.await?;
let tokenlist = DrkTokenList::new(&[
("drk", include_bytes!("../../../contrib/token/darkfi_token_list.min.json")),
("btc", include_bytes!("../../../contrib/token/bitcoin_token_list.min.json")),
("eth", include_bytes!("../../../contrib/token/erc20_token_list.min.json")),
("sol", include_bytes!("../../../contrib/token/solana_token_list.min.json")),
])?;
// P2P network. The faucet doesn't participate in consensus, so we only
// build the sync protocol.
let network_settings = net::Settings {
@@ -374,10 +381,12 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
info!("Registering block sync P2P protocols...");
let _state = state.clone();
let _tokenlist = tokenlist.clone();
registry
.register(net::SESSION_ALL, move |channel, p2p| {
let state = _state.clone();
async move { ProtocolSync::init(channel, state, p2p, false).await.unwrap() }
let tokenlist = _tokenlist.clone();
async move { ProtocolSync::init(channel, state, tokenlist, p2p, false).await.unwrap() }
})
.await;
@@ -415,7 +424,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
})
.detach();
match block_sync_task(sync_p2p.clone(), state.clone()).await {
match block_sync_task(sync_p2p.clone(), state.clone(), &tokenlist).await {
Ok(()) => *faucetd.synced.lock().await = true,
Err(e) => error!("Failed syncing blockchain: {}", e),
}

View File

@@ -9,7 +9,7 @@ use darkfi::{
note::{EncryptedNote, Note},
nullifier::Nullifier,
proof::{ProvingKey, VerifyingKey},
token_id::generate_id2,
token_id::generate_id,
OwnCoin, OwnCoins,
},
node::state::{state_transition, ProgramState, StateUpdate},
@@ -143,7 +143,7 @@ fn main() -> Result<()> {
};
let token_id =
generate_id2("So11111111111111111111111111111111111111112", &NetworkName::Solana)?;
generate_id(&NetworkName::Solana, "So11111111111111111111111111111111111111112")?;
let builder = TransactionBuilder {
clear_inputs: vec![TransactionBuilderClearInputInfo {

View File

@@ -4,7 +4,9 @@ CREATE TABLE IF NOT EXISTS coins(
coin_blind BLOB NOT NULL,
valcom_blind BLOB NOT NULL,
value BLOB NOT NULL,
token_id BLOB NOT NULL,
network BLOB NOT NULL,
drk_address BLOB NOT NULL,
net_address BLOB NOT NULL,
secret BLOB NOT NULL,
is_spent BOOLEAN NOT NULL,
nullifier BLOB NOT NULL,

View File

@@ -8,6 +8,7 @@ use crate::{
block::{BlockInfo, BlockOrder, BlockResponse},
ValidatorState, ValidatorStatePtr,
},
crypto::token_list::DrkTokenList,
net::{
ChannelPtr, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr,
ProtocolJobsManager, ProtocolJobsManagerPtr,
@@ -25,6 +26,7 @@ pub struct ProtocolSync {
block_sub: MessageSubscription<BlockInfo>,
jobsman: ProtocolJobsManagerPtr,
state: ValidatorStatePtr,
tokenlist: DrkTokenList,
p2p: P2pPtr,
consensus_mode: bool,
}
@@ -33,6 +35,7 @@ impl ProtocolSync {
pub async fn init(
channel: ChannelPtr,
state: ValidatorStatePtr,
tokenlist: DrkTokenList,
p2p: P2pPtr,
consensus_mode: bool,
) -> Result<ProtocolBasePtr> {
@@ -49,6 +52,7 @@ impl ProtocolSync {
block_sub,
jobsman: ProtocolJobsManager::new("SyncProtocol", channel),
state,
tokenlist,
p2p,
consensus_mode,
}))
@@ -133,7 +137,13 @@ impl ProtocolSync {
debug!("ProtocolSync::handle_receive_block(): All state transitions passed");
debug!("ProtocolSync::handle_receive_block(): Updating canon state machine");
match self.state.write().await.update_canon_state(state_updates, None).await {
match self
.state
.write()
.await
.update_canon_state(state_updates, &self.tokenlist, None)
.await
{
Ok(()) => {}
Err(e) => {
error!("handle_receive_block(): Canon statemachine update fail: {}", e);

View File

@@ -21,6 +21,7 @@ use crate::{
address::Address,
keypair::{PublicKey, SecretKey},
schnorr::{SchnorrPublic, SchnorrSecret},
token_list::DrkTokenList,
},
net,
node::{
@@ -855,6 +856,7 @@ impl ValidatorState {
pub async fn update_canon_state(
&self,
updates: Vec<StateUpdate>,
tokenlist: &DrkTokenList,
notify: Option<async_channel::Sender<(PublicKey, u64)>>,
) -> Result<()> {
let secret_keys: Vec<SecretKey> =
@@ -864,7 +866,13 @@ impl ValidatorState {
let mut state = self.state_machine.lock().await;
for update in updates {
state
.apply(update, secret_keys.clone(), notify.clone(), self.client.wallet.clone())
.apply(
update,
secret_keys.clone(),
notify.clone(),
self.client.wallet.clone(),
tokenlist,
)
.await?;
}
drop(state);

View File

@@ -3,6 +3,7 @@ use crate::{
block::{BlockOrder, BlockResponse},
ValidatorState, ValidatorStatePtr,
},
crypto::token_list::DrkTokenList,
net,
node::MemoryState,
Result,
@@ -10,7 +11,11 @@ use crate::{
use log::{debug, info, warn};
/// async task used for block syncing.
pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Result<()> {
pub async fn block_sync_task(
p2p: net::P2pPtr,
state: ValidatorStatePtr,
tokenlist: &DrkTokenList,
) -> Result<()> {
info!("Starting blockchain sync...");
// we retrieve p2p network connected channels, so we can use it to
@@ -58,7 +63,7 @@ pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Resu
debug!("block_sync_task(): All state transitions passed");
debug!("block_sync_task(): Updating canon state");
state.write().await.update_canon_state(canon_updates, None).await?;
state.write().await.update_canon_state(canon_updates, tokenlist, None).await?;
debug!("block_sync_task(): Appending blocks to ledger");
state.write().await.blockchain.add(&resp.blocks)?;

View File

@@ -1,5 +1,3 @@
use std::io;
use crypto_api_chachapoly::ChachaPolyIetf;
use rand::rngs::OsRng;
@@ -9,19 +7,16 @@ use crate::{
keypair::{PublicKey, SecretKey},
types::*,
},
util::serial::{Decodable, Encodable, ReadExt, WriteExt},
util::serial::{Decodable, Encodable, SerialDecodable, SerialEncodable},
Error, Result,
};
pub const NOTE_PLAINTEXT_SIZE: usize = 32 + // serial
8 + // value
32 + // token_id
32 + // coin_blind
32; // value_blind
/// Plaintext size is serial + value + token_id + coin_blind + value_blind
pub const NOTE_PLAINTEXT_SIZE: usize = 32 + 8 + 32 + 32 + 32;
pub const AEAD_TAG_SIZE: usize = 16;
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
pub struct Note {
pub serial: DrkSerial,
pub value: u64,
@@ -30,30 +25,6 @@ pub struct Note {
pub value_blind: DrkValueBlind,
}
impl Encodable for Note {
fn encode<S: io::Write>(&self, mut s: S) -> Result<usize> {
let mut len = 0;
len += self.serial.encode(&mut s)?;
len += self.value.encode(&mut s)?;
len += self.token_id.encode(&mut s)?;
len += self.coin_blind.encode(&mut s)?;
len += self.value_blind.encode(&mut s)?;
Ok(len)
}
}
impl Decodable for Note {
fn decode<D: io::Read>(mut d: D) -> Result<Self> {
Ok(Self {
serial: Decodable::decode(&mut d)?,
value: Decodable::decode(&mut d)?,
token_id: Decodable::decode(&mut d)?,
coin_blind: Decodable::decode(&mut d)?,
value_blind: Decodable::decode(d)?,
})
}
}
impl Note {
pub fn encrypt(&self, public: &PublicKey) -> Result<EncryptedNote> {
let ephem_secret = SecretKey::random(&mut OsRng);
@@ -76,30 +47,12 @@ impl Note {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, SerialEncodable, SerialDecodable)]
pub struct EncryptedNote {
ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
ephem_public: PublicKey,
}
impl Encodable for EncryptedNote {
fn encode<S: io::Write>(&self, mut s: S) -> Result<usize> {
let mut len = 0;
s.write_slice(&self.ciphertext)?;
len += ENC_CIPHERTEXT_SIZE;
len += self.ephem_public.encode(&mut s)?;
Ok(len)
}
}
impl Decodable for EncryptedNote {
fn decode<D: io::Read>(mut d: D) -> Result<Self> {
let mut ciphertext = [0u8; ENC_CIPHERTEXT_SIZE];
d.read_slice(&mut ciphertext[..])?;
Ok(Self { ciphertext, ephem_public: Decodable::decode(d)? })
}
}
impl EncryptedNote {
pub fn decrypt(&self, secret: &SecretKey) -> Result<Note> {
let shared_secret = sapling_ka_agree(secret, &self.ephem_public);

View File

@@ -1,74 +1,27 @@
use sha2::{Digest, Sha256};
use group::ff::PrimeField;
use super::types::DrkTokenId;
use crate::{
util::{
serial::{deserialize, serialize},
NetworkName,
},
Result,
};
use crate::{util::NetworkName, Result};
/// Hash the external token ID and NetworkName param.
/// If it fails, change the last 4 bytes and hash it again.
/// Keep repeating until it works.
pub fn generate_id(tkn_str: &str, network: &NetworkName) -> Result<DrkTokenId> {
let mut id_string = network.to_string();
id_string.push_str(tkn_str);
let mut data: Vec<u8> = serialize(&id_string);
let token_id = match deserialize::<DrkTokenId>(&data) {
Ok(v) => v,
Err(_) => {
let mut counter = 0;
loop {
data.truncate(28);
let serialized_counter = serialize(&counter);
data.extend(serialized_counter.iter());
let mut hasher = Sha256::new();
hasher.update(&data);
let hash = hasher.finalize();
let token_id = deserialize::<DrkTokenId>(&hash);
if token_id.is_err() {
counter += 1;
continue
}
return Ok(token_id.unwrap())
}
pub fn generate_id(network: &NetworkName, token_str: &str) -> Result<DrkTokenId> {
let mut net_bytes: Vec<u8> = network.to_string().as_bytes().to_vec();
// TODO: Check for fixed length token_str
let mut token_bytes = match network {
NetworkName::DarkFi => {
let bytes = bs58::decode(token_str).into_vec()?;
return Ok(DrkTokenId::from_repr(bytes.try_into().unwrap()).unwrap())
}
NetworkName::Bitcoin => bs58::decode(token_str).into_vec()?,
NetworkName::Ethereum => hex::decode(token_str.strip_prefix("0x").unwrap())?,
NetworkName::Solana => bs58::decode(token_str).into_vec()?,
};
Ok(token_id)
}
/// YOLO
pub fn generate_id2(tkn_str: &str, network: &NetworkName) -> Result<DrkTokenId> {
let mut num = 0_u64;
match network {
NetworkName::Solana => {
for i in ['s', 'o', 'l'] {
num += i as u64;
}
}
NetworkName::Bitcoin => {
for i in ['b', 't', 'c'] {
num += i as u64;
}
}
NetworkName::Ethereum => {
for i in ['e', 't', 'h'] {
num += i as u64;
}
}
NetworkName::Empty => unimplemented!(),
}
for i in tkn_str.chars() {
num += i as u64;
}
Ok(DrkTokenId::from(num))
net_bytes.append(&mut token_bytes);
// Since our circuit is built to take a 2^64-1 range, we take the first 64
// bits of the hash and make an unsigned integer from it, which we can then
// cast into pallas::Base.
let data: [u8; 8] = blake3::hash(&net_bytes).as_bytes()[0..8].try_into()?;
Ok(DrkTokenId::from(u64::from_le_bytes(data)))
}

View File

@@ -1,272 +1,77 @@
use std::str::FromStr;
use fxhash::FxHashMap;
use group::ff::PrimeField;
use serde_json::Value;
use crate::{
crypto::{token_id::generate_id2, types::DrkTokenId},
util::NetworkName,
Error, Result,
};
use super::{token_id::generate_id, types::DrkTokenId};
use crate::{util::NetworkName, Result};
pub const ETH_NATIVE_TOKEN_ID: &str = "0x0000000000000000000000000000000000000000";
#[derive(Debug, Clone)]
pub struct TokenList {
tokens: Vec<Value>,
#[derive(Clone, Debug)]
pub struct TokenInfo {
pub net_address: String,
pub drk_address: DrkTokenId,
pub name: String,
pub decimals: u64,
}
#[derive(Clone)]
pub struct TokenList(pub FxHashMap<String, TokenInfo>);
impl TokenList {
pub fn new(data: &[u8]) -> Result<Self> {
/// Create a new `TokenList` given a standard JSON object (as bytes)
pub fn new(network_name: &str, data: &[u8]) -> Result<Self> {
let tokenlist: Value = serde_json::from_slice(data)?;
let tokens = tokenlist["tokens"].as_array().ok_or(Error::TokenParseError)?.clone();
Ok(Self { tokens })
let mut map = FxHashMap::default();
for i in tokenlist["tokens"].as_array().unwrap() {
let net_address = i["address"].as_str().unwrap().to_string();
let decimals = i["decimals"].as_u64().unwrap();
let name = i["name"].as_str().unwrap().to_string();
let drk_address = generate_id(&NetworkName::from_str(network_name)?, &net_address)?;
let info = TokenInfo { net_address, drk_address, decimals, name };
let ticker = i["symbol"].as_str().unwrap().to_uppercase().to_string();
map.insert(ticker, info);
}
Ok(Self(map))
}
pub fn get_symbols(&self) -> Result<Vec<String>> {
let mut symbols: Vec<String> = Vec::new();
for item in self.tokens.iter() {
let symbol = item["symbol"].as_str().unwrap();
symbols.push(symbol.to_string());
/// Tries to find the address and name of a given ticker in
/// the hashmap.
pub fn get(&self, ticker: String) -> Option<TokenInfo> {
if let Some(info) = self.0.get(&ticker) {
return Some(info.clone())
}
Ok(symbols)
}
pub fn search_id(&self, symbol: &str) -> Result<Option<String>> {
for item in self.tokens.iter() {
if item["symbol"] == symbol.to_uppercase() {
let address = item["address"].clone();
let address = address.as_str().ok_or(Error::TokenParseError)?;
return Ok(Some(address.to_string()))
}
}
Ok(None)
}
pub fn search_decimal(&self, symbol: &str) -> Result<Option<usize>> {
for item in self.tokens.iter() {
if item["symbol"] == symbol.to_uppercase() {
let decimals = item["decimals"].clone();
let decimals = decimals.as_u64().ok_or(Error::TokenParseError)?;
let decimals = decimals as usize;
return Ok(Some(decimals))
}
}
Ok(None)
None
}
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct DrkTokenList {
pub tokens: FxHashMap<NetworkName, FxHashMap<String, DrkTokenId>>,
pub by_net: FxHashMap<NetworkName, TokenList>,
pub by_addr: FxHashMap<String, (NetworkName, TokenInfo)>,
}
impl DrkTokenList {
pub fn new(sol_list: &TokenList, eth_list: &TokenList, btc_list: &TokenList) -> Result<Self> {
let sol_symbols = sol_list.get_symbols()?;
let eth_symbols = eth_list.get_symbols()?;
let btc_symbols = btc_list.get_symbols()?;
pub fn new(data: &[(&str, &[u8])]) -> Result<Self> {
let mut by_net = FxHashMap::default();
let mut by_addr = FxHashMap::default();
let sol_tokens: FxHashMap<String, DrkTokenId> = sol_symbols
.iter()
.filter_map(|symbol| {
Self::generate_hash_pair(sol_list, &NetworkName::Solana, symbol).ok()
})
.collect();
let eth_tokens: FxHashMap<String, DrkTokenId> = eth_symbols
.iter()
.filter_map(|symbol| {
Self::generate_hash_pair(eth_list, &NetworkName::Ethereum, symbol).ok()
})
.collect();
let btc_tokens: FxHashMap<String, DrkTokenId> = btc_symbols
.iter()
.filter_map(|symbol| {
Self::generate_hash_pair(btc_list, &NetworkName::Bitcoin, symbol).ok()
})
.collect();
let mut tokens: FxHashMap<NetworkName, FxHashMap<String, DrkTokenId>> =
FxHashMap::default();
tokens.insert(NetworkName::Solana, sol_tokens);
tokens.insert(NetworkName::Ethereum, eth_tokens);
tokens.insert(NetworkName::Bitcoin, btc_tokens);
Ok(Self { tokens })
}
fn generate_hash_pair(
token_list: &TokenList,
network_name: &NetworkName,
symbol: &str,
) -> Result<(String, DrkTokenId)> {
if let Some(token_id) = &token_list.search_id(symbol)? {
return Ok((symbol.to_string(), generate_id2(token_id, network_name)?))
};
Err(Error::UnsupportedToken)
}
pub fn symbol_from_id(&self, id: &DrkTokenId) -> Result<Option<(NetworkName, String)>> {
for (network, tokens) in self.tokens.iter() {
for (key, val) in tokens.iter() {
if val == id {
return Ok(Some((network.clone(), key.clone())))
}
for (name, json) in data {
let net_name = NetworkName::from_str(name)?;
let tokenlist = TokenList::new(name, json)?;
for (_, token) in tokenlist.0.iter() {
by_addr.insert(
bs58::encode(token.drk_address.to_repr()).into_string(),
(net_name.clone(), token.clone()),
);
}
by_net.insert(net_name, tokenlist);
}
Ok(None)
}
}
pub fn assign_id(
network: &NetworkName,
token: &str,
sol_tokenlist: &TokenList,
eth_tokenlist: &TokenList,
btc_tokenlist: &TokenList,
) -> Result<String> {
match network {
NetworkName::Solana => {
// (== 44) can represent a Solana base58 token mint address
if token.len() == 44 {
Ok(token.to_string())
} else {
let tok_lower = token.to_lowercase();
symbol_to_id(&tok_lower, sol_tokenlist)
}
}
NetworkName::Bitcoin => {
if token.len() == 34 {
Ok(token.to_string())
} else {
let tok_lower = token.to_lowercase();
symbol_to_id(&tok_lower, btc_tokenlist)
}
}
NetworkName::Ethereum => {
// (== 42) can represent a erc20 token mint address
if token.len() == 42 {
Ok(token.to_string())
} else if token == "eth" {
Ok(ETH_NATIVE_TOKEN_ID.to_string())
} else {
let tok_lower = token.to_lowercase();
symbol_to_id(&tok_lower, eth_tokenlist)
}
}
_ => Err(Error::UnsupportedCoinNetwork),
}
}
pub fn symbol_to_id(token: &str, tokenlist: &TokenList) -> Result<String> {
let vec: Vec<char> = token.chars().collect();
let mut counter = 0;
for c in vec {
if c.is_alphabetic() {
counter += 1;
}
}
if counter == token.len() {
if let Some(id) = tokenlist.search_id(token)? {
Ok(id)
} else {
Err(Error::TokenParseError)
}
} else {
Ok(token.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _get_sol_tokens() -> Result<TokenList> {
let file_contents = include_bytes!("../../tests/data/solanatokenlisttest.json");
let sol_tokenlist: Value = serde_json::from_slice(file_contents)?;
let tokens = sol_tokenlist["tokens"].as_array().ok_or(Error::TokenParseError)?.clone();
let sol_tokenlist = TokenList { tokens };
Ok(sol_tokenlist)
}
fn _get_eth_tokens() -> Result<TokenList> {
let file_contents = include_bytes!("../../tests/data/erc20tokenlisttest.json");
let eth_tokenlist: Value = serde_json::from_slice(file_contents)?;
let tokens = eth_tokenlist["tokens"].as_array().ok_or(Error::TokenParseError)?.clone();
let eth_tokenlist = TokenList { tokens };
Ok(eth_tokenlist)
}
fn _get_btc_tokens() -> Result<TokenList> {
let file_contents = include_bytes!("../../contrib/token/bitcoin_token_list.json");
let btc_tokenlist: Value = serde_json::from_slice(file_contents)?;
let tokens = btc_tokenlist["tokens"].as_array().ok_or(Error::TokenParseError)?.clone();
let btc_tokenlist = TokenList { tokens };
Ok(btc_tokenlist)
}
#[test]
pub fn test_get_symbols() -> Result<()> {
let tokens = _get_sol_tokens()?;
let symbols = tokens.get_symbols()?;
assert_eq!(symbols.len(), 5);
assert_eq!("MILLI", symbols[0]);
assert_eq!("ZI", symbols[1]);
assert_eq!("SOLA", symbols[2]);
assert_eq!("SOL", symbols[3]);
assert_eq!("USDC", symbols[4]);
Ok(())
}
#[test]
pub fn test_get_id_from_symbols() -> Result<()> {
let tokens = _get_sol_tokens()?;
let symbol = &tokens.get_symbols()?[3];
let id = tokens.search_id(symbol)?;
assert!(id.is_some());
assert_eq!(id.unwrap(), "So11111111111111111111111111111111111111112");
Ok(())
}
#[test]
pub fn test_hashmap() -> Result<()> {
let sol_tokens = _get_sol_tokens()?;
let sol_tokens2 = _get_sol_tokens()?;
let eth_tokens = _get_eth_tokens()?;
let eth_tokens2 = _get_eth_tokens()?;
let btc_tokens = _get_btc_tokens()?;
let btc_tokens2 = _get_btc_tokens()?;
let drk_token = DrkTokenList::new(&sol_tokens, &eth_tokens, &btc_tokens)?;
assert_eq!(drk_token.tokens[&NetworkName::Solana].len(), 5);
assert_eq!(drk_token.tokens[&NetworkName::Ethereum].len(), 3);
assert_eq!(drk_token.tokens[&NetworkName::Bitcoin].len(), 1);
assert_eq!(
drk_token.tokens[&NetworkName::Solana]["SOL"],
generate_id2(&sol_tokens2.search_id("SOL")?.unwrap(), &NetworkName::Solana)?
);
assert_eq!(
drk_token.tokens[&NetworkName::Bitcoin]["BTC"],
generate_id2(&btc_tokens2.search_id("BTC")?.unwrap(), &NetworkName::Bitcoin)?
);
assert_eq!(
drk_token.tokens[&NetworkName::Ethereum]["WBTC"],
generate_id2(&eth_tokens2.search_id("WBTC")?.unwrap(), &NetworkName::Ethereum)?
);
Ok(())
Ok(Self { by_net, by_addr })
}
}

View File

@@ -46,6 +46,9 @@ pub enum Error {
#[error("Could not parse token parameter")]
TokenParseError,
#[error(transparent)]
TryFromSliceError(#[from] std::array::TryFromSliceError),
// ===============
// Encoding errors
// ===============
@@ -80,6 +83,10 @@ pub enum Error {
#[error(transparent)]
Bs58DecodeError(#[from] bs58::decode::Error),
#[cfg(feature = "hex")]
#[error(transparent)]
HexDecodeError(#[from] hex::FromHexError),
#[error("Bad operation type byte")]
BadOperationType,

View File

@@ -11,6 +11,7 @@ use crate::{
note::{EncryptedNote, Note},
nullifier::Nullifier,
proof::VerifyingKey,
token_list::DrkTokenList,
OwnCoin,
},
tx::Transaction,
@@ -137,6 +138,7 @@ impl State {
secret_keys: Vec<SecretKey>,
notify: Option<async_channel::Sender<(PublicKey, u64)>>,
wallet: WalletPtr,
tokenlist: &DrkTokenList,
) -> Result<()> {
debug!(target: "state_apply", "Extend nullifier set");
self.nullifiers.insert(&update.nullifiers)?;
@@ -158,7 +160,7 @@ impl State {
let own_coin =
OwnCoin { coin, note, secret: *secret, nullifier, leaf_position };
wallet.put_own_coin(own_coin).await?;
wallet.put_own_coin(own_coin, tokenlist).await?;
if let Some(ch) = notify.clone() {
debug!(target: "state_apply", "Send a notification");

View File

@@ -9,15 +9,18 @@ use crate::{
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum NetworkName {
DarkFi,
Solana,
Bitcoin,
Ethereum,
Empty,
}
impl std::fmt::Display for NetworkName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DarkFi => {
write!(f, "DarkFi")
}
Self::Solana => {
write!(f, "Solana")
}
@@ -27,9 +30,6 @@ impl std::fmt::Display for NetworkName {
Self::Ethereum => {
write!(f, "Ethereum")
}
Self::Empty => {
write!(f, "No Supported Network")
}
}
}
}
@@ -39,6 +39,7 @@ impl FromStr for NetworkName {
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"drk" | "darkfi" => Ok(NetworkName::DarkFi),
"sol" | "solana" => Ok(NetworkName::Solana),
"btc" | "bitcoin" => Ok(NetworkName::Bitcoin),
"eth" | "ethereum" => Ok(NetworkName::Ethereum),

View File

@@ -1,8 +1,9 @@
use std::{fs::create_dir_all, path::Path, str::FromStr, time::Duration};
use async_std::sync::Arc;
use group::ff::PrimeField;
use incrementalmerkletree::bridgetree::BridgeTree;
use log::{debug, error, info, LevelFilter};
use log::{debug, error, info, warn, LevelFilter};
use rand::rngs::OsRng;
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode},
@@ -17,12 +18,14 @@ use crate::{
merkle_node::MerkleNode,
note::Note,
nullifier::Nullifier,
token_list::DrkTokenList,
types::DrkTokenId,
OwnCoin, OwnCoins,
},
util::{
expand_path,
serial::{deserialize, serialize},
NetworkName,
},
Error::{WalletEmptyPassword, WalletTreeExists},
Result,
@@ -267,7 +270,7 @@ impl WalletDb {
// TODO: FIXME:
let value_bytes: Vec<u8> = row.get("value");
let value = u64::from_le_bytes(value_bytes.try_into().unwrap());
let token_id = deserialize(row.get("token_id"))?;
let token_id = deserialize(row.get("drk_address"))?;
let note = Note { serial, value, token_id, coin_blind, value_blind };
let secret = deserialize(row.get("secret"))?;
@@ -282,7 +285,7 @@ impl WalletDb {
Ok(own_coins)
}
pub async fn put_own_coin(&self, own_coin: OwnCoin) -> Result<()> {
pub async fn put_own_coin(&self, own_coin: OwnCoin, tokenlist: &DrkTokenList) -> Result<()> {
debug!("Putting own coin into wallet database");
let coin = serialize(&own_coin.coin.to_bytes());
@@ -290,27 +293,38 @@ impl WalletDb {
let coin_blind = serialize(&own_coin.note.coin_blind);
let value_blind = serialize(&own_coin.note.value_blind);
let value = own_coin.note.value.to_le_bytes();
let token_id = serialize(&own_coin.note.token_id);
let drk_address = serialize(&own_coin.note.token_id);
let secret = serialize(&own_coin.secret);
let is_spent: u32 = 0;
let nullifier = serialize(&own_coin.nullifier);
let leaf_position = serialize(&own_coin.leaf_position);
let is_spent: u32 = 0;
let token_id_enc = bs58::encode(&own_coin.note.token_id.to_repr()).into_string();
let (network, net_address) =
if let Some((network, token_info)) = tokenlist.by_addr.get(&token_id_enc) {
(network, token_info.net_address.clone())
} else {
warn!("Could not find network and token info in parsed token list");
(&NetworkName::DarkFi, "unknown".to_string())
};
let mut conn = self.conn.acquire().await?;
sqlx::query(
"INSERT OR REPLACE INTO coins
(coin, serial, value, token_id, coin_blind,
valcom_blind, secret, is_spent, nullifier,
leaf_position)
(coin, serial, coin_blind, valcom_blind, value,
network, drk_address, net_address,
secret, is_spent, nullifier, leaf_position)
VALUES
(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10);",
(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);",
)
.bind(coin)
.bind(serial)
.bind(value.to_vec())
.bind(token_id)
.bind(coin_blind)
.bind(value_blind)
.bind(value.to_vec())
.bind(serialize(network))
.bind(drk_address) // token_id
.bind(net_address)
.bind(secret)
.bind(is_spent)
.bind(nullifier)
@@ -348,10 +362,11 @@ impl WalletDb {
let is_spent = 0;
let mut conn = self.conn.acquire().await?;
let rows = sqlx::query("SELECT value, token_id, nullifier FROM coins WHERE is_spent = ?1;")
.bind(is_spent)
.fetch_all(&mut conn)
.await?;
let rows =
sqlx::query("SELECT value, drk_address, nullifier FROM coins WHERE is_spent = ?1;")
.bind(is_spent)
.fetch_all(&mut conn)
.await?;
debug!("Found {} rows", rows.len());
@@ -360,7 +375,7 @@ impl WalletDb {
// TODO: FIXME:
let value_bytes: Vec<u8> = row.get("value");
let value = u64::from_le_bytes(value_bytes.try_into().unwrap());
let token_id = deserialize(row.get("token_id"))?;
let token_id = deserialize(row.get("drk_address"))?;
let nullifier = deserialize(row.get("nullifier"))?;
list.push(Balance { token_id, value, nullifier });
}
@@ -373,14 +388,14 @@ impl WalletDb {
let is_spent = 0;
let mut conn = self.conn.acquire().await?;
let rows = sqlx::query("SELECT token_id FROM coins WHERE is_spent = ?1;")
let rows = sqlx::query("SELECT drk_address FROM coins WHERE is_spent = ?1;")
.bind(is_spent)
.fetch_all(&mut conn)
.await?;
let mut token_ids = vec![];
for row in rows {
let token_id = deserialize(row.get("token_id"))?;
let token_id = deserialize(row.get("drk_address"))?;
token_ids.push(token_id);
}
@@ -395,7 +410,7 @@ impl WalletDb {
let mut conn = self.conn.acquire().await?;
let id_check = sqlx::query("SELECT * FROM coins WHERE token_id = ?1 AND is_spent = ?2;")
let id_check = sqlx::query("SELECT * FROM coins WHERE drk_address = ?1 AND is_spent = ?2;")
.bind(id)
.bind(is_spent)
.fetch_optional(&mut conn)
@@ -448,6 +463,13 @@ mod tests {
let wallet = WalletDb::new("sqlite::memory:", WPASS).await?;
let keypair = Keypair::random(&mut OsRng);
let tokenlist = DrkTokenList::new(&[
("drk", include_bytes!("../../contrib/token/darkfi_token_list.min.json")),
("btc", include_bytes!("../../contrib/token/bitcoin_token_list.min.json")),
("eth", include_bytes!("../../contrib/token/erc20_token_list.min.json")),
("sol", include_bytes!("../../contrib/token/solana_token_list.min.json")),
])?;
// init_db()
wallet.init_db().await?;
@@ -465,19 +487,19 @@ mod tests {
let c3 = dummy_coin(&keypair.secret, 11, &token_id);
// put_own_coin()
wallet.put_own_coin(c0).await?;
wallet.put_own_coin(c0, &tokenlist).await?;
tree1.append(&MerkleNode::from_coin(&c0.coin));
tree1.witness();
wallet.put_own_coin(c1).await?;
wallet.put_own_coin(c1, &tokenlist).await?;
tree1.append(&MerkleNode::from_coin(&c1.coin));
tree1.witness();
wallet.put_own_coin(c2).await?;
wallet.put_own_coin(c2, &tokenlist).await?;
tree1.append(&MerkleNode::from_coin(&c2.coin));
tree1.witness();
wallet.put_own_coin(c3).await?;
wallet.put_own_coin(c3, &tokenlist).await?;
tree1.append(&MerkleNode::from_coin(&c3.coin));
tree1.witness();