Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(leaser): migrate leases in batches #99

Merged
merged 6 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions contracts/leaser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "leaser"
version = "0.3.0"
version = "0.4.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
Expand Down Expand Up @@ -33,9 +33,10 @@ thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }

[dev-dependencies]
lease = { workspace = true, features = ["testing"]}
lease = { workspace = true, features = ["testing"] }
finance = { workspace = true, features = ["testing"] }
currency = { workspace = true, features = ["testing"] }
platform = { workspace = true, features = ["testing"] }
schema = { workspace = true }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
Expand Down
16 changes: 12 additions & 4 deletions contracts/leaser/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ pub fn execute(
currency,
max_ltd,
),
ExecuteMsg::MigrateLeases { new_code_id } => {
SingleUserAccess::check_owner_access(deps.storage, &info.sender)
.and_then(move |()| leaser::try_migrate_leases(deps.storage, new_code_id.u64()))
}
ExecuteMsg::MigrateLeases {
new_code_id,
max_leases,
} => SingleUserAccess::check_owner_access(deps.storage, &info.sender).and_then(move |()| {
leaser::try_migrate_leases(deps.storage, new_code_id.u64(), max_leases)
}),
ExecuteMsg::MigrateLeasesCont {
key: start_past,
max_leases,
} => SingleUserAccess::check_owner_access(deps.storage, &info.sender).and_then(move |()| {
leaser::try_migrate_leases_cont(deps.storage, start_past, max_leases)
}),
}
.map(response::response_only_messages)
}
Expand Down
5 changes: 5 additions & 0 deletions contracts/leaser/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::num::TryFromIntError;

use thiserror::Error;

use sdk::cosmwasm_std::StdError;
Expand All @@ -7,6 +9,9 @@ pub enum ContractError {
#[error("[Leaser] [Std] {0}")]
Std(#[from] StdError),

#[error("[Leaser] integer conversion {0}")]
Conversion(#[from] TryFromIntError),

#[error("[Leaser] {0}")]
Finance(#[from] finance::error::Error),

Expand Down
48 changes: 41 additions & 7 deletions contracts/leaser/src/leaser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use finance::{currency::SymbolOwned, liability::Liability, percent::Percent};
use lease::api::{ConnectionParams, DownpaymentCoin, InterestPaymentSpec};
use lpp::{msg::ExecuteMsg, stub::LppRef};
use oracle::stub::OracleRef;
use platform::batch::Batch;
use platform::batch::{Batch, Emit, Emitter};
use platform::message::Response as MessageResponse;
use sdk::cosmwasm_std::{Addr, Deps, StdResult, Storage};

use crate::{
cmd::Quote,
error::ContractError,
migrate::{self},
msg::{ConfigResponse, QuoteResponse},
msg::{ConfigResponse, NbInstances, QuoteResponse},
result::ContractResult,
state::{config::Config, leases::Leases},
};
Expand Down Expand Up @@ -92,24 +92,58 @@ pub(super) fn try_configure(
pub(super) fn try_migrate_leases(
storage: &mut dyn Storage,
new_code_id: u64,
max_leases: NbInstances,
) -> ContractResult<MessageResponse> {
Config::update_lease_code(storage, new_code_id)?;

migrate::migrate_leases(Leases::iter(storage), new_code_id)
.and_then(|batch| update_lpp(storage, new_code_id, batch))
let leases = Leases::iter(storage, None);
migrate::migrate_leases(leases, new_code_id, max_leases)
.and_then(|result| result.try_add_msgs(|msgs| update_lpp_impl(storage, new_code_id, msgs)))
.map(|result| {
MessageResponse::messages_with_events(result.msgs, emit_status(result.last_instance))
})
}

pub(super) fn update_lpp(
pub(super) fn try_migrate_leases_cont(
storage: &mut dyn Storage,
start_past: Addr,
max_leases: NbInstances,
) -> ContractResult<MessageResponse> {
let lease_code_id = Config::load(storage)?.lease_code_id;

let leases = Leases::iter(storage, Some(start_past));
migrate::migrate_leases(leases, lease_code_id, max_leases).map(|result| {
MessageResponse::messages_with_events(result.msgs, emit_status(result.last_instance))
})
}

pub(super) fn update_lpp(
storage: &dyn Storage,
new_code_id: u64,
mut batch: Batch,
) -> ContractResult<MessageResponse> {
) -> ContractResult<Batch> {
update_lpp_impl(storage, new_code_id, &mut batch).map(|()| batch)
}

fn update_lpp_impl(
storage: &dyn Storage,
new_code_id: u64,
batch: &mut Batch,
) -> ContractResult<()> {
let lpp = Config::load(storage)?.lpp_addr;
let lpp_update_code = ExecuteMsg::NewLeaseCode {
lease_code_id: new_code_id.into(),
};
batch
.schedule_execute_wasm_no_reply::<_, Nls>(&lpp, lpp_update_code, None)
.map(|()| batch.into())
.map_err(Into::into)
}

fn emit_status(last_instance: Option<Addr>) -> Emitter {
let emitter = Emitter::of_type("migrate-leases");
if let Some(last) = last_instance {
emitter.emit("contunuation-key", last)
} else {
emitter.emit("status", "done")
}
}
134 changes: 108 additions & 26 deletions contracts/leaser/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,97 @@ use lease::api::MigrateMsg;
use platform::batch::Batch;
use sdk::cosmwasm_std::Addr;

use crate::{error::ContractError, result::ContractResult};
use crate::{error::ContractError, msg::NbInstances, result::ContractResult};
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved

pub fn migrate_leases<I>(leases: I, lease_code_id: u64) -> ContractResult<Batch>
#[derive(Default)]
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
pub struct MigrationResult {
pub msgs: Batch,
/// None if the number of processed instances is less than the `max_leases`
pub last_instance: Option<Addr>,
}

pub fn migrate_leases<I>(
leases: I,
lease_code_id: u64,
max_leases: NbInstances,
) -> ContractResult<MigrationResult>
where
I: Iterator<Item = ContractResult<Addr>>,
{
let no_msgs = MigrateBatch::new(lease_code_id);
let migrated_msgs = leases.fold(no_msgs, MigrateBatch::migrate_lease);
let no_msgs = MigrateBatch::new(lease_code_id, max_leases);
let migrated_msgs = leases
.take(max_leases.try_into()?)
.fold(no_msgs, MigrateBatch::migrate_lease);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
migrated_msgs.try_into()
}

impl MigrationResult {
pub fn try_add_msgs<F>(mut self, add_fn: F) -> ContractResult<Self>
where
F: FnOnce(&mut Batch) -> ContractResult<()>,
{
add_fn(&mut self.msgs).map(|()| self)
}
}
struct MigrateBatch {
new_code_id: u64,
may_batch: ContractResult<Batch>,
leases_left: NbInstances,
may_result: ContractResult<MigrationResult>,
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}
impl MigrateBatch {
fn new(new_code_id: u64) -> Self {
fn new(new_code_id: u64, max_leases: NbInstances) -> Self {
Self {
new_code_id,
may_batch: Ok(Batch::default()),
leases_left: max_leases,
may_result: Ok(Default::default()),
}
}

fn migrate_lease(mut self, lease_contract: ContractResult<Addr>) -> Self {
let op = |mut batch: Batch| {
let op = |result: MigrationResult| {
lease_contract.and_then(|lease| {
batch
.schedule_migrate_wasm_no_reply(&lease, MigrateMsg {}, self.new_code_id)
.map(|_| batch)
.map_err(Into::into)
self.leases_left -= 1;
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
result
.try_add_msgs(|msgs| {
msgs.schedule_migrate_wasm_no_reply(&lease, MigrateMsg {}, self.new_code_id)
.map_err(Into::into)
})
.map(|mut res| {
if self.leases_left == NbInstances::MIN {
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
res.last_instance = Some(lease);
}
res
})
})
};

self.may_batch = self.may_batch.and_then(op);
self.may_result = self.may_result.and_then(op);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
self
}
}

impl TryFrom<MigrateBatch> for Batch {
impl TryFrom<MigrateBatch> for MigrationResult {
type Error = ContractError;
fn try_from(this: MigrateBatch) -> Result<Self, Self::Error> {
this.may_batch
this.may_result
}
}

#[cfg(test)]
mod test {
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
use lease::api::MigrateMsg;
use platform::batch::Batch;
use sdk::cosmwasm_std::Addr;

use crate::ContractError;
use crate::{migrate::MigrationResult, ContractError};

#[test]
fn no_leases() {
let new_code = 242;
let no_leases = vec![];
assert_eq!(
Ok(Batch::default()),
super::migrate_leases(no_leases.into_iter().map(Ok), new_code)
Ok(MigrationResult::default()),
super::migrate_leases(no_leases.into_iter().map(Ok), 2, new_code)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -77,7 +108,7 @@ mod test {
];
assert_eq!(
Err(ContractError::ParseError { err: err.into() }),
super::migrate_leases(no_leases.into_iter(), new_code)
super::migrate_leases(no_leases.into_iter(), 5, new_code)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -88,15 +119,66 @@ mod test {
let addr2 = Addr::unchecked("22222");
let leases = vec![addr1.clone(), addr2.clone()];

let mut exp = Batch::default();
exp.schedule_migrate_wasm_no_reply(&addr1, MigrateMsg {}, new_code)
.unwrap();
exp.schedule_migrate_wasm_no_reply(&addr2, MigrateMsg {}, new_code)
.unwrap();
let exp = add_expected(MigrationResult::default(), &addr1, new_code);
let mut exp = add_expected(exp, &addr2, new_code);
exp.last_instance = Some(addr2);

assert_eq!(
Ok(exp),
super::migrate_leases(leases.into_iter().map(Ok), new_code)
super::migrate_leases(leases.into_iter().map(Ok), new_code, 2)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
fn paging() {
let new_code = 242;
let addr1 = Addr::unchecked("11111");
let addr2 = Addr::unchecked("22222");
let addr3 = Addr::unchecked("333333333");
let addr4 = Addr::unchecked("4");
let addr5 = Addr::unchecked("5555");
let addr6 = Addr::unchecked("6");
let addr7 = Addr::unchecked("777");
let leases: Vec<Addr> = [&addr1, &addr2, &addr3, &addr4, &addr5, &addr6, &addr7]
.map(Clone::clone)
.into();

{
let exp = add_expected(MigrationResult::default(), &addr1, new_code);
let mut exp = add_expected(exp, &addr2, new_code);
exp.last_instance = Some(addr2);
assert_eq!(
Ok(exp),
super::migrate_leases(leases.clone().into_iter().map(Ok), new_code, 2)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}

{
let exp = add_expected(MigrationResult::default(), &addr3, new_code);
let exp = add_expected(exp, &addr4, new_code);
let mut exp = add_expected(exp, &addr5, new_code);
exp.last_instance = Some(addr5);
assert_eq!(
Ok(exp),
super::migrate_leases(leases.clone().into_iter().skip(2).map(Ok), new_code, 3)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}

{
let exp = add_expected(MigrationResult::default(), &addr6, new_code);
let mut exp = add_expected(exp, &addr7, new_code);
exp.last_instance = None;
assert_eq!(
Ok(exp),
super::migrate_leases(leases.into_iter().skip(2 + 3).map(Ok), new_code, 15)
);
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
}
}

fn add_expected(mut exp: MigrationResult, lease_addr: &Addr, new_code: u64) -> MigrationResult {
exp.msgs
.schedule_migrate_wasm_no_reply(lease_addr, MigrateMsg {}, new_code)
.unwrap();
exp
}
}
18 changes: 18 additions & 0 deletions contracts/leaser/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub struct InstantiateMsg {
#[derive(Serialize, Deserialize)]
pub struct MigrateMsg {}

pub type NbInstances = u32;
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved
gmanev7 marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Expand All @@ -33,9 +35,25 @@ pub enum ExecuteMsg {
#[serde(default)]
max_ltd: Option<Percent>,
},
/// Start a Lease migration
///
/// The consumed gas is a limitaton factor for the maximum lease instances that
/// can be processed in a transaction. For that reason, the process does the migration
/// in batches. A new batch is started with this transaction. It processes the specified
/// maximum number of leases and emits a continuation key as an event
/// 'wasm-migrate-leases.contunuation-key=<key>'. That key should be provided
/// with the next `MigrateLeasesCont` message. It in turn emits
/// a continuation key with the same event and the procedure continues until
/// no key is provided and a 'wasm-migrate-leases.status=done'.
MigrateLeases {
new_code_id: Uint64,
max_leases: NbInstances,
},
/// Continue a Lease migration
///
/// It migrates the next batch of up to `max_leases` number of Lease instances
/// and emits the status as specified in `MigrateLeases`.
MigrateLeasesCont { key: Addr, max_leases: NbInstances },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
Expand Down
Loading