mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-13 06:14:44 +00:00
Smart Contract Scheduler
This commit is contained in:
parent
0616085109
commit
72a18bf8bb
12 changed files with 241 additions and 226 deletions
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
|
@ -48,6 +48,7 @@ jobs:
|
||||||
-p serai-processor-utxo-scheduler-primitives \
|
-p serai-processor-utxo-scheduler-primitives \
|
||||||
-p serai-processor-utxo-scheduler \
|
-p serai-processor-utxo-scheduler \
|
||||||
-p serai-processor-transaction-chaining-scheduler \
|
-p serai-processor-transaction-chaining-scheduler \
|
||||||
|
-p serai-processor-smart-contract-scheduler \
|
||||||
-p serai-processor-signers \
|
-p serai-processor-signers \
|
||||||
-p serai-processor-bin \
|
-p serai-processor-bin \
|
||||||
-p serai-bitcoin-processor \
|
-p serai-bitcoin-processor \
|
||||||
|
|
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -8350,18 +8350,25 @@ name = "serai-ethereum-processor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"borsh",
|
"borsh",
|
||||||
"const-hex",
|
"ciphersuite",
|
||||||
"env_logger",
|
"dkg",
|
||||||
"ethereum-serai",
|
"ethereum-serai",
|
||||||
|
"flexible-transcript",
|
||||||
"hex",
|
"hex",
|
||||||
"k256",
|
"k256",
|
||||||
"log",
|
"log",
|
||||||
|
"modular-frost",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
|
"rand_core",
|
||||||
|
"serai-client",
|
||||||
"serai-db",
|
"serai-db",
|
||||||
"serai-env",
|
"serai-processor-bin",
|
||||||
"serai-message-queue",
|
"serai-processor-key-gen",
|
||||||
"serai-processor-messages",
|
"serai-processor-primitives",
|
||||||
"serde_json",
|
"serai-processor-scanner",
|
||||||
|
"serai-processor-scheduler-primitives",
|
||||||
|
"serai-processor-signers",
|
||||||
|
"serai-processor-smart-contract-scheduler",
|
||||||
"tokio",
|
"tokio",
|
||||||
"zalloc",
|
"zalloc",
|
||||||
]
|
]
|
||||||
|
@ -8781,6 +8788,20 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-processor-smart-contract-scheduler"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"borsh",
|
||||||
|
"group",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"serai-db",
|
||||||
|
"serai-primitives",
|
||||||
|
"serai-processor-primitives",
|
||||||
|
"serai-processor-scanner",
|
||||||
|
"serai-processor-scheduler-primitives",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-processor-tests"
|
name = "serai-processor-tests"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -81,6 +81,7 @@ members = [
|
||||||
"processor/scheduler/utxo/primitives",
|
"processor/scheduler/utxo/primitives",
|
||||||
"processor/scheduler/utxo/standard",
|
"processor/scheduler/utxo/standard",
|
||||||
"processor/scheduler/utxo/transaction-chaining",
|
"processor/scheduler/utxo/transaction-chaining",
|
||||||
|
"processor/scheduler/smart-contract",
|
||||||
"processor/signers",
|
"processor/signers",
|
||||||
|
|
||||||
"processor/bin",
|
"processor/bin",
|
||||||
|
|
|
@ -55,6 +55,7 @@ exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-utxo-scheduler-primitives" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-utxo-scheduler-primitives" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-standard-scheduler" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-standard-scheduler" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-transaction-chaining-scheduler" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-transaction-chaining-scheduler" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-smart-contract-scheduler" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-signers" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-signers" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-bitcoin-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-bitcoin-processor" },
|
||||||
|
|
|
@ -17,27 +17,38 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
const-hex = { version = "1", default-features = false }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
serde_json = { version = "1", default-features = false, features = ["std"] }
|
|
||||||
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std", "recommended"] }
|
||||||
|
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
||||||
|
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] }
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||||
|
|
||||||
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
||||||
ethereum-serai = { path = "../../networks/ethereum", default-features = false, optional = true }
|
ethereum-serai = { path = "../../networks/ethereum", default-features = false, optional = true }
|
||||||
|
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
serai-client = { path = "../../substrate/client", default-features = false, features = ["bitcoin"] }
|
||||||
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }
|
|
||||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] }
|
|
||||||
|
|
||||||
zalloc = { path = "../../common/zalloc" }
|
zalloc = { path = "../../common/zalloc" }
|
||||||
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] }
|
||||||
|
|
||||||
serai-db = { path = "../../common/db" }
|
serai-db = { path = "../../common/db" }
|
||||||
serai-env = { path = "../../common/env" }
|
|
||||||
|
|
||||||
messages = { package = "serai-processor-messages", path = "../messages" }
|
key-gen = { package = "serai-processor-key-gen", path = "../key-gen" }
|
||||||
|
|
||||||
message-queue = { package = "serai-message-queue", path = "../../message-queue" }
|
primitives = { package = "serai-processor-primitives", path = "../primitives" }
|
||||||
|
scheduler = { package = "serai-processor-scheduler-primitives", path = "../scheduler/primitives" }
|
||||||
|
scanner = { package = "serai-processor-scanner", path = "../scanner" }
|
||||||
|
smart-contract-scheduler = { package = "serai-processor-smart-contract-scheduler", path = "../scheduler/smart-contract" }
|
||||||
|
signers = { package = "serai-processor-signers", path = "../signers" }
|
||||||
|
|
||||||
|
bin = { package = "serai-processor-bin", path = "../bin" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
parity-db = ["serai-db/parity-db"]
|
parity-db = ["bin/parity-db"]
|
||||||
rocksdb = ["serai-db/rocksdb"]
|
rocksdb = ["bin/rocksdb"]
|
||||||
|
|
34
processor/scheduler/smart-contract/Cargo.toml
Normal file
34
processor/scheduler/smart-contract/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-processor-smart-contract-scheduler"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Scheduler for a smart contract representing the Serai processor"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/scheduler/smart-contract"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = []
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["scale", "borsh"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
group = { version = "0.13", default-features = false }
|
||||||
|
|
||||||
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||||
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
|
|
||||||
|
serai-primitives = { path = "../../../substrate/primitives", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
serai-db = { path = "../../../common/db" }
|
||||||
|
|
||||||
|
primitives = { package = "serai-processor-primitives", path = "../../primitives" }
|
||||||
|
scanner = { package = "serai-processor-scanner", path = "../../scanner" }
|
||||||
|
scheduler-primitives = { package = "serai-processor-scheduler-primitives", path = "../primitives" }
|
15
processor/scheduler/smart-contract/LICENSE
Normal file
15
processor/scheduler/smart-contract/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2024 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
3
processor/scheduler/smart-contract/README.md
Normal file
3
processor/scheduler/smart-contract/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Smart Contract Scheduler
|
||||||
|
|
||||||
|
A scheduler for a smart contract representing the Serai processor.
|
136
processor/scheduler/smart-contract/src/lib.rs
Normal file
136
processor/scheduler/smart-contract/src/lib.rs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use core::{marker::PhantomData, future::Future};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use group::GroupEncoding;
|
||||||
|
|
||||||
|
use serai_db::{Get, DbTxn, create_db};
|
||||||
|
|
||||||
|
use primitives::{ReceivedOutput, Payment};
|
||||||
|
use scanner::{
|
||||||
|
LifetimeStage, ScannerFeed, KeyFor, AddressFor, EventualityFor, BlockFor, SchedulerUpdate,
|
||||||
|
KeyScopedEventualities, Scheduler as SchedulerTrait,
|
||||||
|
};
|
||||||
|
use scheduler_primitives::*;
|
||||||
|
|
||||||
|
create_db! {
|
||||||
|
SmartContractScheduler {
|
||||||
|
NextNonce: () -> u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A smart contract.
|
||||||
|
pub trait SmartContract<S: ScannerFeed>: 'static + Send {
|
||||||
|
/// The type representing a signable transaction.
|
||||||
|
type SignableTransaction: SignableTransaction;
|
||||||
|
|
||||||
|
/// Rotate from the retiring key to the new key.
|
||||||
|
fn rotate(
|
||||||
|
nonce: u64,
|
||||||
|
retiring_key: KeyFor<S>,
|
||||||
|
new_key: KeyFor<S>,
|
||||||
|
) -> (Self::SignableTransaction, EventualityFor<S>);
|
||||||
|
/// Fulfill the set of payments, dropping any not worth handling.
|
||||||
|
fn fulfill(
|
||||||
|
starting_nonce: u64,
|
||||||
|
payments: Vec<Payment<AddressFor<S>>>,
|
||||||
|
) -> Vec<(Self::SignableTransaction, EventualityFor<S>)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scheduler for a smart contract representing the Serai processor.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct Scheduler<S: ScannerFeed, SC: SmartContract<S>> {
|
||||||
|
_S: PhantomData<S>,
|
||||||
|
_SC: PhantomData<SC>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fulfill_payments<S: ScannerFeed, SC: SmartContract<S>>(
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||||
|
payments: Vec<Payment<AddressFor<S>>>,
|
||||||
|
) -> KeyScopedEventualities<S> {
|
||||||
|
let key = match active_keys[0].1 {
|
||||||
|
LifetimeStage::ActiveYetNotReporting |
|
||||||
|
LifetimeStage::Active |
|
||||||
|
LifetimeStage::UsingNewForChange => active_keys[0].0,
|
||||||
|
LifetimeStage::Forwarding | LifetimeStage::Finishing => active_keys[1].0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut nonce = NextNonce::get(txn).unwrap_or(0);
|
||||||
|
let mut eventualities = Vec::with_capacity(1);
|
||||||
|
for (signable, eventuality) in SC::fulfill(nonce, payments) {
|
||||||
|
TransactionsToSign::<SC::SignableTransaction>::send(txn, &key, &signable);
|
||||||
|
nonce += 1;
|
||||||
|
eventualities.push(eventuality);
|
||||||
|
}
|
||||||
|
NextNonce::set(txn, &nonce);
|
||||||
|
HashMap::from([(key.to_bytes().as_ref().to_vec(), eventualities)])
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ScannerFeed, SC: SmartContract<S>> SchedulerTrait<S> for Scheduler<S, SC> {
|
||||||
|
type EphemeralError = ();
|
||||||
|
type SignableTransaction = SC::SignableTransaction;
|
||||||
|
|
||||||
|
fn activate_key(_txn: &mut impl DbTxn, _key: KeyFor<S>) {}
|
||||||
|
|
||||||
|
fn flush_key(
|
||||||
|
&self,
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
_block: &BlockFor<S>,
|
||||||
|
retiring_key: KeyFor<S>,
|
||||||
|
new_key: KeyFor<S>,
|
||||||
|
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||||
|
async move {
|
||||||
|
let nonce = NextNonce::get(txn).unwrap_or(0);
|
||||||
|
let (signable, eventuality) = SC::rotate(nonce, retiring_key, new_key);
|
||||||
|
NextNonce::set(txn, &(nonce + 1));
|
||||||
|
TransactionsToSign::<SC::SignableTransaction>::send(txn, &retiring_key, &signable);
|
||||||
|
Ok(HashMap::from([(retiring_key.to_bytes().as_ref().to_vec(), vec![eventuality])]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retire_key(_txn: &mut impl DbTxn, _key: KeyFor<S>) {}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
_block: &BlockFor<S>,
|
||||||
|
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||||
|
update: SchedulerUpdate<S>,
|
||||||
|
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||||
|
async move {
|
||||||
|
// We ignore the outputs as we don't need to know our current state as it never suffers
|
||||||
|
// partial availability
|
||||||
|
|
||||||
|
// We shouldn't have any forwards though
|
||||||
|
assert!(update.forwards().is_empty());
|
||||||
|
|
||||||
|
// Create the transactions for the returns
|
||||||
|
Ok(fulfill_payments::<S, SC>(
|
||||||
|
txn,
|
||||||
|
active_keys,
|
||||||
|
update
|
||||||
|
.returns()
|
||||||
|
.iter()
|
||||||
|
.map(|to_return| {
|
||||||
|
Payment::new(to_return.address().clone(), to_return.output().balance(), None)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fulfill(
|
||||||
|
&self,
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
_block: &BlockFor<S>,
|
||||||
|
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||||
|
payments: Vec<Payment<AddressFor<S>>>,
|
||||||
|
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||||
|
async move { Ok(fulfill_payments::<S, SC>(txn, active_keys, payments)) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -470,7 +470,7 @@ impl<S: ScannerFeed, P: TransactionPlanner<S, ()>> SchedulerTrait<S> for Schedul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the transactions for the forwards/burns
|
// Create the transactions for the forwards/returns
|
||||||
{
|
{
|
||||||
let mut planned_txs = vec![];
|
let mut planned_txs = vec![];
|
||||||
for forward in update.forwards() {
|
for forward in update.forwards() {
|
||||||
|
|
|
@ -488,7 +488,7 @@ impl<S: ScannerFeed, P: TransactionPlanner<S, EffectedReceivedOutputs<S>>> Sched
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the transactions for the forwards/burns
|
// Create the transactions for the forwards/returns
|
||||||
{
|
{
|
||||||
let mut planned_txs = vec![];
|
let mut planned_txs = vec![];
|
||||||
for forward in update.forwards() {
|
for forward in update.forwards() {
|
||||||
|
|
|
@ -1,208 +0,0 @@
|
||||||
use std::{io, collections::HashSet};
|
|
||||||
|
|
||||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
|
||||||
|
|
||||||
use serai_client::primitives::{NetworkId, Coin, Balance};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Get, DbTxn, Db, Payment, Plan, create_db,
|
|
||||||
networks::{Output, Network},
|
|
||||||
multisigs::scheduler::{SchedulerAddendum, Scheduler as SchedulerTrait},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct Scheduler<N: Network> {
|
|
||||||
key: <N::Curve as Ciphersuite>::G,
|
|
||||||
coins: HashSet<Coin>,
|
|
||||||
rotated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub enum Addendum<N: Network> {
|
|
||||||
Nonce(u64),
|
|
||||||
RotateTo { nonce: u64, new_key: <N::Curve as Ciphersuite>::G },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Network> SchedulerAddendum for Addendum<N> {
|
|
||||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
|
||||||
let mut kind = [0xff];
|
|
||||||
reader.read_exact(&mut kind)?;
|
|
||||||
match kind[0] {
|
|
||||||
0 => {
|
|
||||||
let mut nonce = [0; 8];
|
|
||||||
reader.read_exact(&mut nonce)?;
|
|
||||||
Ok(Addendum::Nonce(u64::from_le_bytes(nonce)))
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
let mut nonce = [0; 8];
|
|
||||||
reader.read_exact(&mut nonce)?;
|
|
||||||
let nonce = u64::from_le_bytes(nonce);
|
|
||||||
|
|
||||||
let new_key = N::Curve::read_G(reader)?;
|
|
||||||
Ok(Addendum::RotateTo { nonce, new_key })
|
|
||||||
}
|
|
||||||
_ => Err(io::Error::other("reading unknown Addendum type"))?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
|
||||||
match self {
|
|
||||||
Addendum::Nonce(nonce) => {
|
|
||||||
writer.write_all(&[0])?;
|
|
||||||
writer.write_all(&nonce.to_le_bytes())
|
|
||||||
}
|
|
||||||
Addendum::RotateTo { nonce, new_key } => {
|
|
||||||
writer.write_all(&[1])?;
|
|
||||||
writer.write_all(&nonce.to_le_bytes())?;
|
|
||||||
writer.write_all(new_key.to_bytes().as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create_db! {
|
|
||||||
SchedulerDb {
|
|
||||||
LastNonce: () -> u64,
|
|
||||||
RotatedTo: (key: &[u8]) -> Vec<u8>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
|
|
||||||
type Addendum = Addendum<N>;
|
|
||||||
|
|
||||||
/// Check if this Scheduler is empty.
|
|
||||||
fn empty(&self) -> bool {
|
|
||||||
self.rotated
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new Scheduler.
|
|
||||||
fn new<D: Db>(
|
|
||||||
_txn: &mut D::Transaction<'_>,
|
|
||||||
key: <N::Curve as Ciphersuite>::G,
|
|
||||||
network: NetworkId,
|
|
||||||
) -> Self {
|
|
||||||
assert!(N::branch_address(key).is_none());
|
|
||||||
assert!(N::change_address(key).is_none());
|
|
||||||
assert!(N::forward_address(key).is_none());
|
|
||||||
|
|
||||||
Scheduler { key, coins: network.coins().iter().copied().collect(), rotated: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a Scheduler from the DB.
|
|
||||||
fn from_db<D: Db>(
|
|
||||||
db: &D,
|
|
||||||
key: <N::Curve as Ciphersuite>::G,
|
|
||||||
network: NetworkId,
|
|
||||||
) -> io::Result<Self> {
|
|
||||||
Ok(Scheduler {
|
|
||||||
key,
|
|
||||||
coins: network.coins().iter().copied().collect(),
|
|
||||||
rotated: RotatedTo::get(db, key.to_bytes().as_ref()).is_some(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_use_branch(&self, _balance: Balance) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schedule<D: Db>(
|
|
||||||
&mut self,
|
|
||||||
txn: &mut D::Transaction<'_>,
|
|
||||||
utxos: Vec<N::Output>,
|
|
||||||
payments: Vec<Payment<N>>,
|
|
||||||
key_for_any_change: <N::Curve as Ciphersuite>::G,
|
|
||||||
force_spend: bool,
|
|
||||||
) -> Vec<Plan<N>> {
|
|
||||||
for utxo in utxos {
|
|
||||||
assert!(self.coins.contains(&utxo.balance().coin));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nonce = LastNonce::get(txn).unwrap_or(1);
|
|
||||||
let mut plans = vec![];
|
|
||||||
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
|
|
||||||
// Once we rotate, all further payments should be scheduled via the new multisig
|
|
||||||
assert!(!self.rotated);
|
|
||||||
plans.push(Plan {
|
|
||||||
key: self.key,
|
|
||||||
inputs: vec![],
|
|
||||||
payments: chunk.to_vec(),
|
|
||||||
change: None,
|
|
||||||
scheduler_addendum: Addendum::Nonce(nonce),
|
|
||||||
});
|
|
||||||
nonce += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're supposed to rotate to the new key, create an empty Plan which will signify the key
|
|
||||||
// update
|
|
||||||
if force_spend && (!self.rotated) {
|
|
||||||
plans.push(Plan {
|
|
||||||
key: self.key,
|
|
||||||
inputs: vec![],
|
|
||||||
payments: vec![],
|
|
||||||
change: None,
|
|
||||||
scheduler_addendum: Addendum::RotateTo { nonce, new_key: key_for_any_change },
|
|
||||||
});
|
|
||||||
nonce += 1;
|
|
||||||
self.rotated = true;
|
|
||||||
RotatedTo::set(
|
|
||||||
txn,
|
|
||||||
self.key.to_bytes().as_ref(),
|
|
||||||
&key_for_any_change.to_bytes().as_ref().to_vec(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
LastNonce::set(txn, &nonce);
|
|
||||||
|
|
||||||
plans
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume_payments<D: Db>(&mut self, _txn: &mut D::Transaction<'_>) -> Vec<Payment<N>> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn created_output<D: Db>(
|
|
||||||
&mut self,
|
|
||||||
_txn: &mut D::Transaction<'_>,
|
|
||||||
_expected: u64,
|
|
||||||
_actual: Option<u64>,
|
|
||||||
) {
|
|
||||||
panic!("Smart Contract Scheduler created a Branch output")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Refund a specific output.
|
|
||||||
fn refund_plan<D: Db>(
|
|
||||||
&mut self,
|
|
||||||
txn: &mut D::Transaction<'_>,
|
|
||||||
output: N::Output,
|
|
||||||
refund_to: N::Address,
|
|
||||||
) -> Plan<N> {
|
|
||||||
let current_key = RotatedTo::get(txn, self.key.to_bytes().as_ref())
|
|
||||||
.and_then(|key_bytes| <N::Curve as Ciphersuite>::read_G(&mut key_bytes.as_slice()).ok())
|
|
||||||
.unwrap_or(self.key);
|
|
||||||
|
|
||||||
let nonce = LastNonce::get(txn).map_or(1, |nonce| nonce + 1);
|
|
||||||
LastNonce::set(txn, &(nonce + 1));
|
|
||||||
Plan {
|
|
||||||
key: current_key,
|
|
||||||
inputs: vec![],
|
|
||||||
payments: vec![Payment { address: refund_to, data: None, balance: output.balance() }],
|
|
||||||
change: None,
|
|
||||||
scheduler_addendum: Addendum::Nonce(nonce),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shim_forward_plan(_output: N::Output, _to: <N::Curve as Ciphersuite>::G) -> Option<Plan<N>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Forward a specific output to the new multisig.
|
|
||||||
///
|
|
||||||
/// Returns None if no forwarding is necessary.
|
|
||||||
fn forward_plan<D: Db>(
|
|
||||||
&mut self,
|
|
||||||
_txn: &mut D::Transaction<'_>,
|
|
||||||
_output: N::Output,
|
|
||||||
_to: <N::Curve as Ciphersuite>::G,
|
|
||||||
) -> Option<Plan<N>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue