Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14865035
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Subscribers
None
View Options
diff --git a/chronik/bitcoinsuite-slp/src/empp/parse.rs b/chronik/bitcoinsuite-slp/src/empp/parse.rs
index d59f3bfc5..fbe25874a 100644
--- a/chronik/bitcoinsuite-slp/src/empp/parse.rs
+++ b/chronik/bitcoinsuite-slp/src/empp/parse.rs
@@ -1,216 +1,254 @@
// Copyright (c) 2023 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
use bitcoinsuite_core::{
error::DataError,
script::{opcode::*, Op, Script},
};
use bytes::Bytes;
use thiserror::Error;
use crate::empp::ParseError::*;
/// Parsed list of byte arrays from the eMPP protocol
pub type EmppData = Vec<Bytes>;
/// Errors when parsing a eMPP tx failed.
#[derive(Clone, Debug, Error, Eq, PartialEq)]
pub enum ParseError {
- /// Script doesn't parse
+ /// Script contains invalidly encoded opcodes (e.g. OP_PUSHDATA1 without a
+ /// size)
#[error("Failed parsing script: {0}")]
DataError(#[from] DataError),
/// Script empty
#[error("Empty script")]
EmptyScript,
/// Script not empty, but didn't get OP_RETURN
#[error("Expected OP_RETURN, but got {0}")]
MissingOpReturn(Opcode),
/// Got OP_RETURN, but then nothing followed
#[error("Empty OP_RETURN")]
EmptyOpReturn,
/// Got OP_RETURN, but then didn't get OP_RESERVED
#[error("Missing OP_RESERVED, but got {0}")]
MissingOpReserved(Opcode),
/// Cannot use single-byte push opcodes in eMPP
#[error(
"Invalid push opcode {0}: OP_0, OP_1NEGATE, OP_RESERVED, OP_1, .. \
OP_16 not allowed"
)]
InvalidPushOpcode(Opcode),
/// Got a non-push opcode, which is not allowed
#[error("Invalid non-push opcode {0}")]
InvalidNonPushOpcode(Opcode),
/// Pushdata may not be empty
#[error("Empty pushdata not allowed (used {0})")]
EmptyPushdata(Opcode),
}
/// Parse the given script according to the eMPP (eCash Multi Pushdata Protocol)
/// protocol.
///
+/// - `Ok(Some(EmppData))` when the script matches the eMPP protocol.
+/// - `Ok(None)` when the script doesn't look like eMPP (e.g. no OP_RETURN).
+/// - `Err(ParseError)` when the script looks like eMPP but failed one of the
+/// requirements.
+///
/// See [`crate::empp`] for details.
-pub fn parse(script: &Script) -> Result<EmppData, ParseError> {
+pub fn parse(script: &Script) -> Result<Option<EmppData>, ParseError> {
+ match parse_with_ignored_err(script) {
+ Ok(empp) => Ok(Some(empp)),
+ Err(err) if should_ignore_err(&err) => Ok(None),
+ Err(err) => Err(err),
+ }
+}
+
+fn parse_with_ignored_err(script: &Script) -> Result<EmppData, ParseError> {
let mut ops = script.iter_ops();
- let op_return = ops.next().ok_or(EmptyScript)??;
- if !matches!(op_return, Op::Code(OP_RETURN)) {
- return Err(MissingOpReturn(op_return.opcode()));
+ let op_return = ops.next().ok_or(EmptyScript)?;
+ if !matches!(op_return, Ok(Op::Code(OP_RETURN))) {
+ return Err(MissingOpReturn(Opcode(script.bytecode()[0])));
}
- let op_reserved = ops.next().ok_or(EmptyOpReturn)??;
- if !matches!(op_reserved, Op::Code(OP_RESERVED)) {
- return Err(MissingOpReserved(op_reserved.opcode()));
+ let op_reserved = ops.next().ok_or(EmptyOpReturn)?;
+ if !matches!(op_reserved, Ok(Op::Code(OP_RESERVED))) {
+ return Err(MissingOpReserved(Opcode(script.bytecode()[1])));
}
let mut empp_data = EmppData::new();
for pushop in ops {
let payload = match pushop? {
Op::Code(OP_0) => return Err(EmptyPushdata(OP_0)),
Op::Code(opcode @ Opcode(OP_1NEGATE::N..=OP_16::N)) => {
return Err(InvalidPushOpcode(opcode))
}
Op::Code(opcode) => return Err(InvalidNonPushOpcode(opcode)),
Op::Push(opcode, payload) if payload.is_empty() => {
return Err(EmptyPushdata(opcode))
}
Op::Push(_, payload) => payload,
};
empp_data.push(payload);
}
Ok(empp_data)
}
-impl ParseError {
- /// Whether this parse error doesn't look like eMPP at all and should be
- /// treated as an unrelated protocol.
- pub fn should_ignore(&self) -> bool {
- matches!(
- self,
- EmptyScript
- | MissingOpReturn(_)
- | EmptyOpReturn
- | MissingOpReserved(_)
- )
- }
+/// Whether this parse error doesn't look like eMPP at all and should be
+/// treated as an unrelated protocol.
+fn should_ignore_err(err: &ParseError) -> bool {
+ matches!(
+ err,
+ EmptyScript | MissingOpReturn(_) | EmptyOpReturn | MissingOpReserved(_),
+ )
}
#[cfg(test)]
mod tests {
use bitcoinsuite_core::{
error::DataError,
- script::{opcode::*, Script},
+ hash::ShaRmd160,
+ script::{opcode::*, PubKey, Script},
};
use bytes::Bytes;
use pretty_assertions::assert_eq;
- use crate::empp::{parse, EmppData, ParseError};
+ use crate::empp::{
+ parse::{parse, parse_with_ignored_err},
+ EmppData, ParseError,
+ };
+
+ #[test]
+ fn test_empp_ignored() {
+ assert_eq!(parse(&Script::default()), Ok(None));
+ assert_eq!(parse(&Script::new(vec![OP_PUSHDATA1::N].into())), Ok(None));
+ assert_eq!(parse(&Script::new(vec![OP_RETURN::N].into())), Ok(None));
+ assert_eq!(
+ parse(&Script::new(vec![OP_RETURN::N, OP_PUSHDATA1::N].into())),
+ Ok(None),
+ );
+ assert_eq!(
+ parse(&Script::new(vec![OP_RETURN::N, OP_0::N].into())),
+ Ok(None),
+ );
+ assert_eq!(
+ parse(&Script::new(
+ vec![OP_RETURN::N, OP_0::N, OP_RESERVED::N].into()
+ )),
+ Ok(None),
+ );
+ assert_eq!(parse(&Script::p2pk(&PubKey::default())), Ok(None));
+ assert_eq!(parse(&Script::p2pkh(&ShaRmd160::default())), Ok(None));
+ assert_eq!(parse(&Script::p2sh(&ShaRmd160::default())), Ok(None));
+ }
#[test]
- fn test_empp() -> Result<(), ParseError> {
+ fn test_empp() {
#[track_caller]
fn check_parse_err(
bytecode: Vec<u8>,
expected_err: ParseError,
should_ignore: bool,
) {
- let actual_err = parse(&Script::new(bytecode.into()))
+ let script = Script::new(bytecode.into());
+ let actual_err = parse_with_ignored_err(&script)
.expect_err("Test expected parse to fail");
assert_eq!(actual_err, expected_err);
- assert_eq!(actual_err.should_ignore(), should_ignore);
+ let ignored = matches!(parse(&script), Ok(None));
+ assert_eq!(ignored, should_ignore);
}
check_parse_err(vec![], ParseError::EmptyScript, true);
check_parse_err(
vec![OP_CHECKSIG::N],
ParseError::MissingOpReturn(OP_CHECKSIG),
true,
);
check_parse_err(vec![OP_RETURN::N], ParseError::EmptyOpReturn, true);
check_parse_err(
vec![OP_RETURN::N, OP_CHECKSIG::N],
ParseError::MissingOpReserved(OP_CHECKSIG),
true,
);
assert_eq!(
- parse(&Script::new(vec![OP_RETURN::N, OP_RESERVED::N].into()))?,
- EmppData::new(),
+ parse(&Script::new(vec![OP_RETURN::N, OP_RESERVED::N].into())),
+ Ok(Some(EmppData::new())),
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_0::N],
ParseError::EmptyPushdata(OP_0),
false,
);
for opcode in [OP_1NEGATE, OP_1, OP_2, OP_16] {
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, opcode.number()],
ParseError::InvalidPushOpcode(opcode),
false,
);
}
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_CHECKSIG::N],
ParseError::InvalidNonPushOpcode(OP_CHECKSIG),
false,
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA1::N, 0],
ParseError::EmptyPushdata(OP_PUSHDATA1),
false,
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA2::N, 0, 0],
ParseError::EmptyPushdata(OP_PUSHDATA2),
false,
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA4::N, 0, 0, 0, 0],
ParseError::EmptyPushdata(OP_PUSHDATA4),
false,
);
assert_eq!(
parse(&Script::new(
vec![OP_RETURN::N, OP_RESERVED::N, 1, 77].into()
- ))?,
- vec![Bytes::from(vec![77])],
+ )),
+ Ok(Some(vec![Bytes::from(vec![77])])),
);
assert_eq!(
parse(&Script::new(
vec![OP_RETURN::N, OP_RESERVED::N, 1, 77, 2, 88, 99].into()
- ))?,
- vec![Bytes::from(vec![77]), Bytes::from(vec![88, 99])],
+ )),
+ Ok(Some(vec![Bytes::from(vec![77]), Bytes::from(vec![88, 99])])),
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, 1, 77, 2, 88, 99, 0],
ParseError::EmptyPushdata(OP_0),
false,
);
assert_eq!(
parse(&Script::new(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA1::N, 1, 77]
.into()
- ))?,
- vec![Bytes::from(vec![77])],
+ )),
+ Ok(Some(vec![Bytes::from(vec![77])])),
);
assert_eq!(
parse(&Script::new(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA2::N, 1, 0, 77]
.into()
- ))?,
- vec![Bytes::from(vec![77])],
+ )),
+ Ok(Some(vec![Bytes::from(vec![77])])),
);
check_parse_err(
vec![OP_RETURN::N, OP_RESERVED::N, OP_PUSHDATA4::N],
ParseError::DataError(DataError::InvalidLength {
expected: 4,
actual: 0,
}),
false,
);
- Ok(())
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, May 22, 00:38 (18 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866224
Default Alt Text
(10 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment