Repository navigation
Expand file tree
/
Copy pathtransaction.rs
More file actions
177 lines (156 loc) · 6.55 KB
/
Copy pathtransaction.rs
File metadata and controls
177 lines (156 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Composing values into sanitized transaction views.
use agave_transaction_view::{
MAGICBLOCK_INSTRUCTION_TRACE_LENGTH, MAX_MAGICBLOCK_ACCOUNT_LOCKS,
transaction_version::{MAGICBLOCK_PREFIX, TransactionVersion},
};
use keeper::TransactionView;
use magic_root_interface::MagicRootInstruction;
use solana_instruction::Instruction;
use solana_message::{
VersionedMessage,
v1::{self, SIGNATURE_SIZE},
};
use solana_pubkey::Pubkey;
use solana_signature::Signature;
use solana_signer::Signer;
use solana_transaction::{Message, Transaction, TransactionError, versioned::VersionedTransaction};
use crate::{Engine, error::EngineError, error::Result};
/// Opaque transaction admitted through the replication verifier trust boundary.
pub struct VerifiedTransaction(pub(crate) TransactionView);
/// Engine-authorized batch verifier for replicated transaction payloads.
#[derive(Clone, Copy)]
pub struct TransactionVerifier {
authority: Pubkey,
}
impl TransactionVerifier {
pub(crate) fn new(authority: Pubkey) -> Self {
Self { authority }
}
/// Sanitizes the batch, checks private-transaction authority, and verifies
/// all signatures. Returns verified inputs only if the entire batch passes.
pub fn verify(&self, transactions: Vec<Vec<u8>>) -> Result<Vec<VerifiedTransaction>> {
let verified = transactions
.into_iter()
.map(|transaction| {
let view = TransactionView::try_new_sanitized(transaction.into(), true)?;
validate_authority(&view, self.authority)?;
Ok(VerifiedTransaction(view))
})
.collect::<Result<Vec<_>>>()?;
let mut signatures = Vec::with_capacity(verified.len());
for transaction in &verified {
signatures.extend(signature_data(&transaction.0));
}
if !Signature::batch_verify(signatures.into_iter()) {
return Err(EngineError::SignatureVerification);
}
Ok(verified)
}
}
/// Converts instructions, messages, or transaction bytes into a sanitized
/// [`TransactionView`]. Signature verification happens at execution submission,
/// not simulation.
pub trait IntoTransactionView {
/// Composes `self` into a sanitized [`TransactionView`], signing with
/// `engine`'s local signer and latest blockhash where applicable.
fn compose(self, engine: &Engine) -> Result<TransactionView>;
}
impl IntoTransactionView for Message {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
let mut transaction = Transaction::new_unsigned(self);
transaction.try_sign(&[engine.signer()], engine.blockhash())?;
transaction.compose(engine)
}
}
impl IntoTransactionView for Transaction {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
let data = wincode::serialize(&self).map_err(wincode::Error::from)?;
data.compose(engine)
}
}
impl IntoTransactionView for &[Instruction] {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
let msg = Message::new(self, Some(&engine.authority()));
msg.compose(engine)
}
}
impl<const N: usize> IntoTransactionView for &[Instruction; N] {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
self.as_slice().compose(engine)
}
}
impl IntoTransactionView for Vec<u8> {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
TransactionView::try_new_sanitized(self.into(), true)?.compose(engine)
}
}
impl IntoTransactionView for TransactionView {
fn compose(self, engine: &Engine) -> Result<TransactionView> {
validate_authority(&self, engine.authority())?;
Ok(self)
}
}
/// Enforces the authority encoded by private Magicblock transactions.
fn validate_authority(view: &TransactionView, authority: Pubkey) -> Result<()> {
if matches!(view.version(), TransactionVersion::Magicblock)
&& view.static_account_keys()[0] != authority
{
return Err(EngineError::SignatureVerification);
}
Ok(())
}
/// Iterates each signature with its signer key and shared serialized message.
fn signature_data(
view: &TransactionView,
) -> impl ExactSizeIterator<Item = (&Signature, &[u8], &[u8])> {
let message = view.message_data();
view.signatures()
.iter()
.zip(view.static_account_keys())
.map(move |(signature, key)| (signature, key.as_ref(), message))
}
/// Verifies each signature for ordinary transaction submission.
///
/// Replication uses [`TransactionVerifier`] instead; trusted local-ledger replay
/// skips verification. Untrusted ledger data must be verified before replay.
pub(super) fn sigverify(view: &TransactionView) -> Result<()> {
for (signature, key, message) in signature_data(view) {
if !signature.verify(key, message) {
return Err(EngineError::SignatureVerification);
}
}
Ok(())
}
/// Composes an Engine-private transaction and signs its final
/// Magicblock wire representation with the Engine authority.
pub(crate) fn magicblock(mut instructions: Vec<Instruction>, engine: &Engine) -> Result<Vec<u8>> {
// Randomize the signed payload: repeated operations can share a blockhash.
instructions.push(MagicRootInstruction::Nonce(rand::random()).compose(engine.authority())?);
let message = v1::Message::try_compile(&engine.authority(), &instructions, engine.blockhash())?;
// Private transactions have larger payloads but still obey runtime
// instruction-trace and account-index limits.
if message.instructions.len() > MAGICBLOCK_INSTRUCTION_TRACE_LENGTH {
return Err(TransactionError::SanitizeFailure.into());
}
if message.account_keys.len() > MAX_MAGICBLOCK_ACCOUNT_LOCKS
|| message
.instructions
.iter()
.any(|ix| ix.accounts.len() > MAX_MAGICBLOCK_ACCOUNT_LOCKS)
{
return Err(TransactionError::TooManyAccountLocks.into());
}
// Reserve the trailing signature slot without signing the V1 prefix, which
// is replaced below before the only signing operation.
let transaction = VersionedTransaction {
signatures: vec![Default::default()],
message: VersionedMessage::V1(message),
};
let mut data = wincode::serialize(&transaction).map_err(wincode::Error::from)?;
// Select the private wire format before signing; its payload limit exceeds V1's.
data[0] = MAGICBLOCK_PREFIX;
let signature_offset = data.len() - SIGNATURE_SIZE;
let signature = engine.signer().sign_message(&data[..signature_offset]);
data[signature_offset..].copy_from_slice(signature.as_ref());
Ok(data)
}