refactor(db!: store the one-off transaction signature by component
This commit is contained in:
@@ -314,9 +314,17 @@ create table if not exists proposal_vote (
|
|||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
|
||||||
create table if not exists proposal_result (
|
-- The signature the vault produced for an approved transaction, by component.
|
||||||
proposal_id integer not null primary key references proposal(id) on delete cascade,
|
--
|
||||||
data blob not null,
|
-- secp256k1 signatures have three common encodings (Electrum v=27/28, raw parity,
|
||||||
|
-- ERC-2098 compact); a single blob would not say which one it holds. `y_parity` is
|
||||||
|
-- the raw bit -- add 27 to rebuild the Electrum form `Signature::as_bytes` emits.
|
||||||
|
create table if not exists proposal_one_off_transaction_result (
|
||||||
|
proposal_id integer not null primary key
|
||||||
|
references proposal_one_off_transaction (proposal_id) on delete cascade,
|
||||||
|
r blob not null check (length(r) = 32),
|
||||||
|
s blob not null check (length(s) = 32),
|
||||||
|
y_parity integer not null check (y_parity in (0, 1)),
|
||||||
created_at integer not null default(unixepoch('now'))
|
created_at integer not null default(unixepoch('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
|||||||
@@ -661,7 +661,6 @@ impl ProposalManager {
|
|||||||
tx: one_off_transaction::Settings,
|
tx: one_off_transaction::Settings,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use crate::actors::evm::ClientSignTransaction;
|
use crate::actors::evm::ClientSignTransaction;
|
||||||
use crate::db::models::NewProposalResult;
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
consensus::TxEip1559,
|
consensus::TxEip1559,
|
||||||
eips::eip2930::AccessList,
|
eips::eip2930::AccessList,
|
||||||
@@ -691,12 +690,7 @@ impl ProposalManager {
|
|||||||
.map_err(|e| Error::ExecutionFailed(format!("sign one-off tx: {e}")))?;
|
.map_err(|e| Error::ExecutionFailed(format!("sign one-off tx: {e}")))?;
|
||||||
|
|
||||||
let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?;
|
let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?;
|
||||||
diesel::insert_into(schema::proposal_result::table)
|
one_off_transaction::store_signature(proposal_id, &sig, &mut conn)
|
||||||
.values(NewProposalResult {
|
|
||||||
proposal_id,
|
|
||||||
data: sig.as_bytes().to_vec(),
|
|
||||||
})
|
|
||||||
.execute(&mut conn)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::ExecutionFailed(format!("store proposal result: {e}")))?;
|
.map_err(|e| Error::ExecutionFailed(format!("store proposal result: {e}")))?;
|
||||||
|
|
||||||
|
|||||||
@@ -515,13 +515,6 @@ pub struct NewProposalVote {
|
|||||||
pub signature: Vec<u8>,
|
pub signature: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Insertable)]
|
|
||||||
#[diesel(table_name = schema::proposal_result, check_for_backend(Sqlite))]
|
|
||||||
pub struct NewProposalResult {
|
|
||||||
pub proposal_id: i32,
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Insertable)]
|
#[derive(Debug, Insertable)]
|
||||||
#[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))]
|
#[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))]
|
||||||
pub struct NewRecoveryProposalVote {
|
pub struct NewRecoveryProposalVote {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
//! Signing a single EIP-1559 transaction.
|
//! Signing a single EIP-1559 transaction.
|
||||||
|
|
||||||
use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed};
|
use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed};
|
||||||
use crate::db::{DatabaseConnection, schema::proposal_one_off_transaction};
|
use crate::db::{
|
||||||
|
DatabaseConnection,
|
||||||
|
schema::{proposal_one_off_transaction, proposal_one_off_transaction_result},
|
||||||
|
};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _,
|
Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _,
|
||||||
sqlite::Sqlite,
|
sqlite::Sqlite,
|
||||||
@@ -100,3 +103,32 @@ impl Proposal for OneOffTransaction {
|
|||||||
row.into_settings()
|
row.into_settings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The signature the vault produced for an approved transaction.
|
||||||
|
#[derive(Debug, Insertable)]
|
||||||
|
#[diesel(table_name = proposal_one_off_transaction_result, check_for_backend(Sqlite))]
|
||||||
|
struct SignatureRow {
|
||||||
|
proposal_id: i32,
|
||||||
|
r: Vec<u8>,
|
||||||
|
s: Vec<u8>,
|
||||||
|
y_parity: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records the signature produced for an approved transaction, by component, so what
|
||||||
|
/// came back is as readable as what was signed.
|
||||||
|
pub async fn store_signature(
|
||||||
|
proposal_id: i32,
|
||||||
|
signature: &alloy::signers::Signature,
|
||||||
|
conn: &mut DatabaseConnection,
|
||||||
|
) -> QueryResult<()> {
|
||||||
|
diesel::insert_into(proposal_one_off_transaction_result::table)
|
||||||
|
.values(&SignatureRow {
|
||||||
|
proposal_id,
|
||||||
|
r: signature.r().to_be_bytes::<32>().to_vec(),
|
||||||
|
s: signature.s().to_be_bytes::<32>().to_vec(),
|
||||||
|
y_parity: i32::from(signature.v()),
|
||||||
|
})
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
.map(drop)
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,9 +270,11 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
proposal_result (proposal_id) {
|
proposal_one_off_transaction_result (proposal_id) {
|
||||||
proposal_id -> Integer,
|
proposal_id -> Integer,
|
||||||
data -> Binary,
|
r -> Binary,
|
||||||
|
s -> Binary,
|
||||||
|
y_parity -> Integer,
|
||||||
created_at -> Integer,
|
created_at -> Integer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,7 +385,7 @@ diesel::joinable!(evm_wallet_access -> program_client (client_id));
|
|||||||
diesel::joinable!(operator -> operator_identity (id));
|
diesel::joinable!(operator -> operator_identity (id));
|
||||||
diesel::joinable!(program_client -> client_metadata (metadata_id));
|
diesel::joinable!(program_client -> client_metadata (metadata_id));
|
||||||
diesel::joinable!(proposal -> operator_identity (initiator_id));
|
diesel::joinable!(proposal -> operator_identity (initiator_id));
|
||||||
diesel::joinable!(proposal_result -> proposal (proposal_id));
|
diesel::joinable!(proposal_one_off_transaction_result -> proposal_one_off_transaction (proposal_id));
|
||||||
diesel::joinable!(proposal_approve_sdk_client -> proposal (proposal_id));
|
diesel::joinable!(proposal_approve_sdk_client -> proposal (proposal_id));
|
||||||
diesel::joinable!(proposal_grant_wallet_access -> proposal (proposal_id));
|
diesel::joinable!(proposal_grant_wallet_access -> proposal (proposal_id));
|
||||||
diesel::joinable!(proposal_replace_operator -> proposal (proposal_id));
|
diesel::joinable!(proposal_replace_operator -> proposal (proposal_id));
|
||||||
@@ -400,7 +402,7 @@ diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
|||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
aead_encrypted,
|
aead_encrypted,
|
||||||
proposal_result,
|
proposal_one_off_transaction_result,
|
||||||
proposal_approve_sdk_client,
|
proposal_approve_sdk_client,
|
||||||
proposal_grant_wallet_access,
|
proposal_grant_wallet_access,
|
||||||
proposal_replace_operator,
|
proposal_replace_operator,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use arbiter_server::{
|
|||||||
use arbiter_server::actors::vault::Bootstrap;
|
use arbiter_server::actors::vault::Bootstrap;
|
||||||
use arbiter_server::db::schema::{
|
use arbiter_server::db::schema::{
|
||||||
aead_encrypted, evm_basic_grant, evm_wallet, evm_wallet_access, operator_identity,
|
aead_encrypted, evm_basic_grant, evm_wallet, evm_wallet_access, operator_identity,
|
||||||
proposal_result, recovery_operator_identity,
|
proposal_one_off_transaction_result, recovery_operator_identity,
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods, QueryDsl, insert_into};
|
use diesel::{ExpressionMethods, QueryDsl, insert_into};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
@@ -790,13 +790,20 @@ async fn approve_one_off_transaction_stores_result() {
|
|||||||
assert_eq!(outcome, VoteOutcome::Approved);
|
assert_eq!(outcome, VoteOutcome::Approved);
|
||||||
|
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let count: i64 = proposal_result::table
|
let (r, s, y_parity): (Vec<u8>, Vec<u8>, i32) = proposal_one_off_transaction_result::table
|
||||||
.filter(proposal_result::proposal_id.eq(proposal_id))
|
.find(proposal_id)
|
||||||
.count()
|
.select((
|
||||||
.get_result(&mut conn)
|
proposal_one_off_transaction_result::r,
|
||||||
|
proposal_one_off_transaction_result::s,
|
||||||
|
proposal_one_off_transaction_result::y_parity,
|
||||||
|
))
|
||||||
|
.first(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.expect("an approved transaction must leave its signature");
|
||||||
assert_eq!(count, 1);
|
|
||||||
|
assert_eq!(r.len(), 32, "r must be a 32-byte scalar");
|
||||||
|
assert_eq!(s.len(), 32, "s must be a 32-byte scalar");
|
||||||
|
assert!(y_parity == 0 || y_parity == 1, "y_parity must be a bit");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Reference in New Issue
Block a user