mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
contract/consensus: laid foundation
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -1230,6 +1230,25 @@ dependencies = [
|
||||
"wasmer-middlewares",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-consensus-contract"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"chacha20poly1305",
|
||||
"darkfi",
|
||||
"darkfi-money-contract",
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"getrandom",
|
||||
"halo2_proofs",
|
||||
"log",
|
||||
"rand",
|
||||
"simplelog",
|
||||
"sled",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-dao-contract"
|
||||
version = "0.4.1"
|
||||
|
||||
@@ -48,6 +48,7 @@ members = [
|
||||
|
||||
"src/contract/money",
|
||||
"src/contract/dao",
|
||||
"src/contract/consensus",
|
||||
|
||||
"example/dchat",
|
||||
]
|
||||
|
||||
@@ -22,7 +22,7 @@ use async_std::sync::{Arc, RwLock};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
constants::MERKLE_DEPTH,
|
||||
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
|
||||
contract_id::{CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
|
||||
schnorr::{SchnorrPublic, SchnorrSecret},
|
||||
MerkleNode, PublicKey, SecretKey,
|
||||
},
|
||||
@@ -150,6 +150,7 @@ impl ValidatorState {
|
||||
// in the money contract.
|
||||
let money_contract_deploy_payload = serialize(&faucet_pubkeys);
|
||||
let dao_contract_deploy_payload = vec![];
|
||||
let consensus_contract_deploy_payload = vec![];
|
||||
|
||||
let native_contracts = vec![
|
||||
(
|
||||
@@ -164,6 +165,12 @@ impl ValidatorState {
|
||||
include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
|
||||
dao_contract_deploy_payload,
|
||||
),
|
||||
(
|
||||
"Consensus Contract",
|
||||
*CONSENSUS_CONTRACT_ID,
|
||||
include_bytes!("../contract/consensus/consensus_contract.wasm").to_vec(),
|
||||
consensus_contract_deploy_payload,
|
||||
),
|
||||
];
|
||||
|
||||
info!(target: "consensus::validator", "Deploying native wasm contracts");
|
||||
|
||||
@@ -8,3 +8,7 @@ This directory contains native WASM contracts on DarkFi.
|
||||
## DAO
|
||||
|
||||
* https://darkrenaissance.github.io/darkfi/development/darkfi_dao_contract/index.html
|
||||
|
||||
## Consensus
|
||||
|
||||
* TODO: add link here
|
||||
|
||||
2
src/contract/consensus/.gitignore
vendored
Normal file
2
src/contract/consensus/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
consensus_contract.wasm
|
||||
proof/*.zk.bin
|
||||
49
src/contract/consensus/Cargo.toml
Normal file
49
src/contract/consensus/Cargo.toml
Normal file
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "darkfi-consensus-contract"
|
||||
version = "0.4.1"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = { path = "../../sdk" }
|
||||
darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
|
||||
darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0.38"
|
||||
|
||||
# The following dependencies are used for the client API and
|
||||
# probably shouldn't be in WASM
|
||||
chacha20poly1305 = { version = "0.10.1", optional = true }
|
||||
darkfi = { path = "../../../", features = ["zk", "rpc", "blockchain"], optional = true }
|
||||
halo2_proofs = { version = "0.2.0", optional = true }
|
||||
log = { version = "0.4.17", optional = true }
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
|
||||
# These are used just for the integration tests
|
||||
[dev-dependencies]
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
darkfi = {path = "../../../", features = ["tx", "blockchain"]}
|
||||
darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
|
||||
simplelog = "0.12.0"
|
||||
sled = "0.34.7"
|
||||
|
||||
# We need to disable random using "custom" which makes the crate a noop
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
no-entrypoint = []
|
||||
client = [
|
||||
"darkfi",
|
||||
"darkfi-money-contract/client",
|
||||
"darkfi-money-contract/no-entrypoint",
|
||||
"rand",
|
||||
"chacha20poly1305",
|
||||
"log",
|
||||
"halo2_proofs",
|
||||
]
|
||||
41
src/contract/consensus/Makefile
Normal file
41
src/contract/consensus/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
.POSIX:
|
||||
|
||||
# Cargo binary
|
||||
CARGO = cargo
|
||||
|
||||
# zkas compiler binary
|
||||
ZKAS = ../../../zkas
|
||||
|
||||
# zkas circuits
|
||||
PROOFS_SRC = $(shell find proof -type f -name '*.zk')
|
||||
PROOFS_BIN = $(PROOFS_SRC:=.bin)
|
||||
|
||||
# wasm source files
|
||||
WASM_SRC = \
|
||||
$(shell find src -type f) \
|
||||
$(shell find ../../sdk -type f) \
|
||||
$(shell find ../../serial -type f)
|
||||
|
||||
# wasm contract binary
|
||||
WASM_BIN = consensus_contract.wasm
|
||||
|
||||
all: $(WASM_BIN)
|
||||
|
||||
$(WASM_BIN): $(WASM_SRC) $(PROOFS_BIN)
|
||||
$(CARGO) build --release --package darkfi-consensus-contract --target wasm32-unknown-unknown
|
||||
cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_consensus_contract.wasm $@
|
||||
|
||||
$(PROOFS_BIN): $(ZKAS) $(PROOFS_SRC)
|
||||
$(ZKAS) $(basename $@) -o $@
|
||||
|
||||
test-stake-unstake: all
|
||||
$(CARGO) test --release --features=no-entrypoint,client \
|
||||
--package darkfi-consensus-contract \
|
||||
--test stake_unstake
|
||||
|
||||
test: test-stake-unstake
|
||||
|
||||
clean:
|
||||
rm -f $(PROOFS_BIN) $(WASM_BIN)
|
||||
|
||||
.PHONY: all test-stake-unstake test clean
|
||||
29
src/contract/consensus/src/client/mod.rs
Normal file
29
src/contract/consensus/src/client/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This module implements the client-side API for this contract's interaction.
|
||||
//! What we basically do here is implement an API that creates the necessary
|
||||
//! structures and is able to export them to create a DarkFi transaction
|
||||
//! object that can be broadcasted to the network.
|
||||
//!
|
||||
//! Note that this API does not involve any wallet interaction, but only takes
|
||||
//! the necessary objects provided by the caller. This is intentional, so we
|
||||
//! are able to abstract away any wallet interfaces to client implementations.
|
||||
|
||||
/// `Consensus::StakeV1` API
|
||||
pub mod stake_v1;
|
||||
19
src/contract/consensus/src/client/stake_v1.rs
Normal file
19
src/contract/consensus/src/client/stake_v1.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This API is crufty. Please rework it into something nice to read and nice to use.
|
||||
119
src/contract/consensus/src/entrypoint.rs
Normal file
119
src/contract/consensus/src/entrypoint.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::ContractId,
|
||||
db::{db_init, db_lookup, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
|
||||
error::{ContractError, ContractResult},
|
||||
msg, ContractCall,
|
||||
};
|
||||
use darkfi_serial::deserialize;
|
||||
|
||||
use crate::{model::ConsensusStakeUpdateV1, ConsensusFunction};
|
||||
|
||||
/// `Consensus::Stake` functions
|
||||
mod stake_v1;
|
||||
use stake_v1::{
|
||||
consensus_stake_get_metadata_v1, consensus_stake_process_instruction_v1,
|
||||
consensus_stake_process_update_v1,
|
||||
};
|
||||
|
||||
darkfi_sdk::define_contract!(
|
||||
init: init_contract,
|
||||
exec: process_instruction,
|
||||
apply: process_update,
|
||||
metadata: get_metadata
|
||||
);
|
||||
|
||||
/// This entrypoint function runs when the contract is (re)deployed and initialized.
|
||||
/// We use this function to initialize all the necessary databases and prepare them
|
||||
/// with initial data if necessary. This is also the place where we bundle the zkas
|
||||
/// circuits that are to be used with functions provided by the contract.
|
||||
fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
|
||||
// The zkas circuit can simply be embedded in the wasm and set up by
|
||||
// the initialization. Note that the tree should then be called "zkas".
|
||||
// The lookups can be done by `contract_id+_zkas+namespace`.
|
||||
// TODO: For the zkas tree, external host checks should be done to ensure
|
||||
// that the bincode is actually valid and not arbitrary.
|
||||
let _zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, SMART_CONTRACT_ZKAS_DB_NAME)?,
|
||||
};
|
||||
|
||||
// TODO: implement
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function is used by the wasm VM's host to fetch the necessary metadata
|
||||
/// for verifying signatures and zk proofs. The payload given here are all the
|
||||
/// contract calls in the transaction.
|
||||
fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
if call_idx >= calls.len() as u32 {
|
||||
msg!("Error: call_idx >= calls.len()");
|
||||
return Err(ContractError::Internal)
|
||||
}
|
||||
|
||||
match ConsensusFunction::try_from(calls[call_idx as usize].data[0])? {
|
||||
ConsensusFunction::StakeV1 => {
|
||||
// We pass everything into the correct function, and it will return
|
||||
// the metadata for us, which we can then copy into the host with
|
||||
// the `set_return_data` function. On the host, this metadata will
|
||||
// be used to do external verification (zk proofs, and signatures).
|
||||
let metadata = consensus_stake_get_metadata_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&metadata)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function verifies a state transition and produces a state update
|
||||
/// if everything is successful. This step should happen **after** the host
|
||||
/// has successfully verified the metadata from `get_metadata()`.
|
||||
fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
if call_idx >= calls.len() as u32 {
|
||||
msg!("Error: call_idx >= calls.len()");
|
||||
return Err(ContractError::Internal)
|
||||
}
|
||||
|
||||
match ConsensusFunction::try_from(calls[call_idx as usize].data[0])? {
|
||||
ConsensusFunction::StakeV1 => {
|
||||
// Again, we pass everything into the correct function.
|
||||
// If it executes successfully, we'll get a state update
|
||||
// which we can copy into the host using `set_return_data`.
|
||||
// This update can then be written with `process_update()`
|
||||
// if everything is in order.
|
||||
let update_data = consensus_stake_process_instruction_v1(cid, call_idx, calls)?;
|
||||
Ok(set_return_data(&update_data)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function attempts to write a given state update provided the previous steps
|
||||
/// of the contract call execution all were successful. It's the last in line, and
|
||||
/// assumes that the transaction/call was successful. The payload given to the function
|
||||
/// is the update data retrieved from `process_instruction()`.
|
||||
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
match ConsensusFunction::try_from(update_data[0])? {
|
||||
ConsensusFunction::StakeV1 => {
|
||||
let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
|
||||
Ok(consensus_stake_process_update_v1(cid, update)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/contract/consensus/src/entrypoint/stake_v1.rs
Normal file
81
src/contract/consensus/src/entrypoint/stake_v1.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{ContractId, PublicKey},
|
||||
error::{ContractError, ContractResult},
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
|
||||
ConsensusFunction,
|
||||
};
|
||||
|
||||
/// `get_metadata` function for `Consensus::StakeV1`
|
||||
pub(crate) fn consensus_stake_get_metadata_v1(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let _params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Public inputs for the ZK proofs we have to verify
|
||||
let zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
// Public keys for the transaction signatures we have to verify
|
||||
let signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
// TODO: implement
|
||||
|
||||
// Serialize everything gathered and return it
|
||||
let mut metadata = vec![];
|
||||
zk_public_inputs.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// `process_instruction` function for `Consensus::StakeV1`
|
||||
pub(crate) fn consensus_stake_process_instruction_v1(
|
||||
_cid: ContractId,
|
||||
_call_idx: u32,
|
||||
_calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
// TODO: implement
|
||||
|
||||
// Create a state update.
|
||||
let update = ConsensusStakeUpdateV1 {};
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(ConsensusFunction::StakeV1 as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
/// `process_update` function for `Consensus::StakeV1`
|
||||
pub(crate) fn consensus_stake_process_update_v1(
|
||||
_cid: ContractId,
|
||||
_update: ConsensusStakeUpdateV1,
|
||||
) -> ContractResult {
|
||||
// TODO: implement
|
||||
|
||||
Ok(())
|
||||
}
|
||||
33
src/contract/consensus/src/error.rs
Normal file
33
src/contract/consensus/src/error.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::error::ContractError;
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum ConsensusError {
|
||||
#[error("Error")]
|
||||
SomeError,
|
||||
}
|
||||
|
||||
impl From<ConsensusError> for ContractError {
|
||||
fn from(e: ConsensusError) -> Self {
|
||||
match e {
|
||||
ConsensusError::SomeError => Self::Custom(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/contract/consensus/src/lib.rs
Normal file
60
src/contract/consensus/src/lib.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Smart contract implementing staking, unstaking and evolving
|
||||
//! of consensus tokens.
|
||||
|
||||
//! Smart contract implementing money transfers, atomic swaps, token
|
||||
//! minting and freezing, and staking/unstaking of consensus tokens.
|
||||
|
||||
use darkfi_sdk::error::ContractError;
|
||||
|
||||
/// Functions available in the contract
|
||||
#[repr(u8)]
|
||||
pub enum ConsensusFunction {
|
||||
StakeV1 = 0x00,
|
||||
//EvolveV1 = 0x01,
|
||||
//UnstakeV1 = 0x02,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ConsensusFunction {
|
||||
type Error = ContractError;
|
||||
|
||||
fn try_from(b: u8) -> core::result::Result<Self, Self::Error> {
|
||||
match b {
|
||||
0x00 => Ok(Self::StakeV1),
|
||||
//0x01 => Ok(Self::EvolveV1),
|
||||
//0x02 => Ok(Self::UnstakeV1),
|
||||
_ => Err(ContractError::InvalidFunction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal contract errors
|
||||
pub mod error;
|
||||
|
||||
/// Call parameters definitions
|
||||
pub mod model;
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
/// WASM entrypoint functions
|
||||
pub mod entrypoint;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Client API for interaction with this smart contract
|
||||
pub mod client;
|
||||
31
src/contract/consensus/src/model.rs
Normal file
31
src/contract/consensus/src/model.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
/// Parameters for `Consensus::Stake`
|
||||
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct ConsensusStakeParamsV1 {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
/// State update for `Consensus::Stake`
|
||||
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct ConsensusStakeUpdateV1 {
|
||||
// TODO: implement
|
||||
}
|
||||
193
src/contract/consensus/tests/harness.rs
Normal file
193
src/contract/consensus/tests/harness.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::collections::HashMap;
|
||||
|
||||
use darkfi::{
|
||||
consensus::{
|
||||
ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
|
||||
TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
|
||||
},
|
||||
tx::Transaction,
|
||||
wallet::{WalletDb, WalletPtr},
|
||||
zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
|
||||
zkas::ZkBinary,
|
||||
Result,
|
||||
};
|
||||
use darkfi_money_contract::client::OwnCoin;
|
||||
use darkfi_sdk::{
|
||||
crypto::{Keypair, MerkleTree, PublicKey, DARK_TOKEN_ID, MONEY_CONTRACT_ID},
|
||||
db::SMART_CONTRACT_ZKAS_DB_NAME,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{serialize, Encodable};
|
||||
use log::warn;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi_money_contract::{
|
||||
client::transfer_v1::TransferCallBuilder, model::MoneyTransferParamsV1, MoneyFunction,
|
||||
MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
|
||||
};
|
||||
|
||||
pub fn init_logger() {
|
||||
let mut cfg = simplelog::ConfigBuilder::new();
|
||||
cfg.add_filter_ignore("sled".to_string());
|
||||
cfg.add_filter_ignore("blockchain::contractstore".to_string());
|
||||
// We check this error so we can execute same file tests in parallel,
|
||||
// otherwise second one fails to init logger here.
|
||||
if let Err(_) = simplelog::TermLogger::init(
|
||||
//simplelog::LevelFilter::Info,
|
||||
simplelog::LevelFilter::Debug,
|
||||
//simplelog::LevelFilter::Trace,
|
||||
cfg.build(),
|
||||
simplelog::TerminalMode::Mixed,
|
||||
simplelog::ColorChoice::Auto,
|
||||
) {
|
||||
warn!(target: "money_harness", "Logger already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Wallet {
|
||||
pub keypair: Keypair,
|
||||
pub state: ValidatorStatePtr,
|
||||
pub merkle_tree: MerkleTree,
|
||||
pub wallet: WalletPtr,
|
||||
pub coins: Vec<OwnCoin>,
|
||||
pub spent_coins: Vec<OwnCoin>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
async fn new(keypair: Keypair, faucet_pubkeys: &[PublicKey]) -> Result<Self> {
|
||||
let wallet = WalletDb::new("sqlite::memory:", "foo").await?;
|
||||
let sled_db = sled::Config::new().temporary(true).open()?;
|
||||
|
||||
let state = ValidatorState::new(
|
||||
&sled_db,
|
||||
*TESTNET_BOOTSTRAP_TIMESTAMP,
|
||||
*TESTNET_GENESIS_TIMESTAMP,
|
||||
*TESTNET_GENESIS_HASH_BYTES,
|
||||
*TESTNET_INITIAL_DISTRIBUTION,
|
||||
wallet.clone(),
|
||||
faucet_pubkeys.to_vec(),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let merkle_tree = MerkleTree::new(100);
|
||||
|
||||
let coins = vec![];
|
||||
let spent_coins = vec![];
|
||||
|
||||
Ok(Self { keypair, state, merkle_tree, wallet, coins, spent_coins })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConsensusTestHarness {
|
||||
pub faucet: Wallet,
|
||||
pub alice: Wallet,
|
||||
pub bob: Wallet,
|
||||
pub charlie: Wallet,
|
||||
pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
|
||||
}
|
||||
|
||||
impl ConsensusTestHarness {
|
||||
pub async fn new() -> Result<Self> {
|
||||
let faucet_kp = Keypair::random(&mut OsRng);
|
||||
let faucet_pubkeys = vec![faucet_kp.public];
|
||||
let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?;
|
||||
|
||||
let alice_kp = Keypair::random(&mut OsRng);
|
||||
let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
|
||||
|
||||
let bob_kp = Keypair::random(&mut OsRng);
|
||||
let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?;
|
||||
|
||||
let charlie_kp = Keypair::random(&mut OsRng);
|
||||
let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?;
|
||||
|
||||
// Get the zkas circuits and build proving keys
|
||||
let mut proving_keys = HashMap::new();
|
||||
let alice_sled = alice.state.read().await.blockchain.sled_db.clone();
|
||||
let db_handle = alice.state.read().await.blockchain.contracts.lookup(
|
||||
&alice_sled,
|
||||
&MONEY_CONTRACT_ID,
|
||||
SMART_CONTRACT_ZKAS_DB_NAME,
|
||||
)?;
|
||||
|
||||
macro_rules! mkpk {
|
||||
($ns:expr) => {
|
||||
let zkbin = db_handle.get(&serialize(&$ns))?.unwrap();
|
||||
let zkbin = ZkBinary::decode(&zkbin)?;
|
||||
let witnesses = empty_witnesses(&zkbin);
|
||||
let circuit = ZkCircuit::new(witnesses, zkbin.clone());
|
||||
let pk = ProvingKey::build(13, &circuit);
|
||||
proving_keys.insert($ns, (pk, zkbin));
|
||||
};
|
||||
}
|
||||
|
||||
mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1);
|
||||
mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1);
|
||||
mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1);
|
||||
mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1);
|
||||
|
||||
Ok(Self { faucet, alice, bob, charlie, proving_keys })
|
||||
}
|
||||
|
||||
pub fn airdrop_native(
|
||||
&self,
|
||||
value: u64,
|
||||
recipient: PublicKey,
|
||||
) -> Result<(Transaction, MoneyTransferParamsV1)> {
|
||||
let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
|
||||
let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
|
||||
|
||||
let builder = TransferCallBuilder {
|
||||
keypair: self.faucet.keypair,
|
||||
recipient,
|
||||
value,
|
||||
token_id: *DARK_TOKEN_ID,
|
||||
rcpt_spend_hook: pallas::Base::zero(),
|
||||
rcpt_user_data: pallas::Base::zero(),
|
||||
rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
|
||||
change_spend_hook: pallas::Base::zero(),
|
||||
change_user_data: pallas::Base::zero(),
|
||||
change_user_data_blind: pallas::Base::random(&mut OsRng),
|
||||
coins: vec![],
|
||||
tree: self.faucet.merkle_tree.clone(),
|
||||
mint_zkbin: mint_zkbin.clone(),
|
||||
mint_pk: mint_pk.clone(),
|
||||
burn_zkbin: burn_zkbin.clone(),
|
||||
burn_pk: burn_pk.clone(),
|
||||
clear_input: true,
|
||||
};
|
||||
|
||||
let debris = builder.build()?;
|
||||
|
||||
let mut data = vec![MoneyFunction::TransferV1 as u8];
|
||||
debris.params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
||||
let proofs = vec![debris.proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok((tx, debris.params))
|
||||
}
|
||||
}
|
||||
78
src/contract/consensus/tests/stake_unstake.rs
Normal file
78
src/contract/consensus/tests/stake_unstake.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Integration test of consensus staking and unstaking for Alice.
|
||||
//!
|
||||
//! We first airdrop Alica native tokes, and then she can stake and unstake
|
||||
//! them a couple of times.
|
||||
//!
|
||||
//! With this test, we want to confirm the consensus contract state
|
||||
//! transitions work for a single party and are able to be verified.
|
||||
//!
|
||||
//! TODO: Malicious cases
|
||||
|
||||
use darkfi::Result;
|
||||
use darkfi_sdk::crypto::{merkle_prelude::*, poseidon_hash, Coin, MerkleNode, Nullifier};
|
||||
use log::info;
|
||||
|
||||
use darkfi_money_contract::client::{MoneyNote, OwnCoin};
|
||||
|
||||
mod harness;
|
||||
use harness::{init_logger, ConsensusTestHarness};
|
||||
|
||||
#[async_std::test]
|
||||
async fn consensus_contract_stake_unstake() -> Result<()> {
|
||||
init_logger();
|
||||
|
||||
const ALICE_AIRDROP: u64 = 1000;
|
||||
|
||||
// Initialize harness
|
||||
let mut th = ConsensusTestHarness::new().await?;
|
||||
info!(target: "money", "[Faucet] ===================================================");
|
||||
info!(target: "money", "[Faucet] Building Money::Transfer params for Alice's airdrop");
|
||||
info!(target: "money", "[Faucet] ===================================================");
|
||||
let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_AIRDROP, th.alice.keypair.public)?;
|
||||
|
||||
info!(target: "money", "[Faucet] ==========================");
|
||||
info!(target: "money", "[Faucet] Executing Alice airdrop tx");
|
||||
info!(target: "money", "[Faucet] ==========================");
|
||||
th.faucet.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
|
||||
th.faucet.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
|
||||
info!(target: "money", "[Alice] ==========================");
|
||||
info!(target: "money", "[Alice] Executing Alice airdrop tx");
|
||||
info!(target: "money", "[Alice] ==========================");
|
||||
th.alice.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
|
||||
th.alice.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
|
||||
|
||||
assert!(th.faucet.merkle_tree.root(0).unwrap() == th.alice.merkle_tree.root(0).unwrap());
|
||||
|
||||
// Gather new owncoins
|
||||
let mut owncoins = vec![];
|
||||
let leaf_position = th.alice.merkle_tree.witness().unwrap();
|
||||
let note: MoneyNote = airdrop_params.outputs[0].note.decrypt(&th.alice.keypair.secret)?;
|
||||
owncoins.push(OwnCoin {
|
||||
coin: Coin::from(airdrop_params.outputs[0].coin),
|
||||
note: note.clone(),
|
||||
secret: th.alice.keypair.secret,
|
||||
nullifier: Nullifier::from(poseidon_hash([th.alice.keypair.secret.inner(), note.serial])),
|
||||
leaf_position,
|
||||
});
|
||||
|
||||
// Thanks for reading
|
||||
Ok(())
|
||||
}
|
||||
@@ -36,6 +36,10 @@ lazy_static! {
|
||||
/// Contract ID for the native DAO contract
|
||||
pub static ref DAO_CONTRACT_ID: ContractId =
|
||||
ContractId::from(poseidon_hash([pallas::Base::zero(), pallas::Base::from(1)]));
|
||||
|
||||
/// Contract ID for the native Consensus contract
|
||||
pub static ref CONSENSUS_CONTRACT_ID: ContractId =
|
||||
ContractId::from(poseidon_hash([pallas::Base::zero(), pallas::Base::from(2)]));
|
||||
}
|
||||
|
||||
/// ContractId represents an on-chain identifier for a certain smart contract.
|
||||
|
||||
Reference in New Issue
Block a user