Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ impl UtxoCoinFields {
posv: self.conf.is_posv,
str_d_zeel,
hash_algo: self.tx_hash_algo.into(),
v_extra_payload: None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo/slp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ impl SlpToken {
posv: unsigned.posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: self.platform_coin.as_ref().tx_hash_algo,
v_extra_payload: None,
};

let _broadcast = self
Expand Down
3 changes: 3 additions & 0 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ async fn p2sh_spending_tx_preimage<T: UtxoCommonOps>(
posv: coin.as_ref().conf.is_posv,
str_d_zeel,
hash_algo,
v_extra_payload: None,
})
}

Expand Down Expand Up @@ -982,6 +983,7 @@ pub async fn p2sh_spending_tx<T: UtxoCommonOps>(coin: &T, input: P2SHSpendingTxI
posv: coin.as_ref().conf.is_posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: unsigned.hash_algo.into(),
v_extra_payload: None,
})
}

Expand Down Expand Up @@ -3768,6 +3770,7 @@ pub async fn tx_details_by_hash<T: UtxoCommonOps>(
let fee = verbose_tx.vin.iter().fold(0., |cur, input| {
let fee = match input {
TransactionInputEnum::Lelantus(lelantus) => lelantus.n_fees,
TransactionInputEnum::Spark(spark) => spark.n_fees,
_ => 0.,
};
cur + fee
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo/utxo_common/utxo_tx_history_v2_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ where
let fee = verbose_tx.vin.iter().fold(0., |cur, input| {
let fee = match input {
TransactionInputEnum::Lelantus(lelantus) => lelantus.n_fees,
TransactionInputEnum::Spark(spark) => spark.n_fees,
_ => 0.,
};
cur + fee
Expand Down
45 changes: 45 additions & 0 deletions mm2src/coins/utxo/utxo_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2833,6 +2833,51 @@ fn firo_lelantus_tx_details() {
assert_eq!(Some(expected_fee), tx_details.fee_details);
}

#[test]
fn firo_spark_tx() {
// https://explorer.firo.org/tx/c50e5a3f16744ac86bacae28d9251a29bf754d250592bce16a953cd961b584d5
let tx_hash = "c50e5a3f16744ac86bacae28d9251a29bf754d250592bce16a953cd961b584d5".into();
let electrum = electrum_client_for_test(&[
"electrumx01.firo.org:50001",
"electrumx02.firo.org:50001",
"electrumx03.firo.org:50001",
]);
let _tx = block_on_f01(electrum.get_verbose_transaction(&tx_hash)).unwrap();
}

#[test]
fn firo_spark_tx_details() {
Comment thread
levoncrypto marked this conversation as resolved.
// https://explorer.firo.org/tx/c50e5a3f16744ac86bacae28d9251a29bf754d250592bce16a953cd961b584d5
let electrum = electrum_client_for_test(&[
"electrumx01.firo.org:50001",
"electrumx02.firo.org:50001",
"electrumx03.firo.org:50001",
]);
let coin = utxo_coin_for_test(electrum.into(), None, false);

let tx_details = get_tx_details_eq_for_both_versions(
&coin,
"c50e5a3f16744ac86bacae28d9251a29bf754d250592bce16a953cd961b584d5",
);

let expected_fee = TxFeeDetails::Utxo(UtxoFeeDetails {
coin: Some(TEST_COIN_NAME.into()),
amount: "0.00003603".parse().unwrap(),
});
assert_eq!(Some(expected_fee), tx_details.fee_details);

let tx_details = get_tx_details_eq_for_both_versions(
&coin,
"3b3da29d2ff910ce15e274355b12ff89917fb98a80f746e4a0bbb669ab732250",
);

let expected_fee = TxFeeDetails::Utxo(UtxoFeeDetails {
coin: Some(TEST_COIN_NAME.into()),
amount: "0.00003603".parse().unwrap(),
});
assert_eq!(Some(expected_fee), tx_details.fee_details);
}

#[test]
fn test_generate_tx_doge_fee() {
// A tx below 1kb is always 0,01 doge fee per kb.
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/utxo_signer/src/sign_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub(crate) fn complete_tx(unsigned: TransactionInputSigner, signed_inputs: Vec<T
posv: unsigned.posv,
str_d_zeel: unsigned.str_d_zeel,
tx_hash_algo: unsigned.hash_algo.into(),
v_extra_payload: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
03000900010000000000000000000000000000000000000000000000000000000000000000ffffffff01d3ffffffff02806297c5010000001976a914bd3dd0b83c5a1e7fe51034f605908a9f2cb2b33f88ac0000000000000000f5d201490af40c59cb005fd45dcf7792dc3c3d5724fcbb178bae1b854168a04da65929010015fd5d9648c64b23364773060b67ba517d34517fbe571e767d0be173b8c2187700005f299dabf2a9c38eec0ea51a1f522504dcf76639194914d4822a7dd4ad2cfca800005a0d6ba857b73eceb2d2085ecdccbad6e06ce156eb398242d62364563c3738f7daf128c5e1a494f334f566c5578501156bd41522c33ff3672a6365f0c1b5a8db6e2a563c6247ee8f33ad36e6ff1586998d33829325a4ded9ded4e310a2bb2ef0a34c471115e43c68b1e88e3e204d0dad7b805d41913248b5cf31c24a24b35238f3aa46af1509dd65de318760f6edd80f00fd470a0107000000000000000107000000000000006474978c121e8d0a7ab8f09bcccc7827c2f2fa481ca471064a93827f5a31aba5130e000000000000013e728e80a5e0032932ceaad63431caf9ba677e4d80ed00475d0c9f0da59309d1000001e8af7d7035ee6cf3b880b71c9e3cec51e9ede84bf291cd9e72b17cbf237bbdcc000001adaf89285f702bff5dcf46f13ee6847b08f8f9ea58f8c12274438be2964edee7010001d886847357e75dc388e6d067572c446dced4883a9ad2e66d25fd6f6b45d2d5ac0000cdec45699ca612920427f723df99233f43d39a62039f9832c0f5cbafcbb226890000058bd005c5f5eae7274328b2cfa681f79c6927b923a712e498040c905ad3a9d88d0000413950f2961d43c740ef019b68a508397163302e6692f3e7101b98440d1118220000eaab97b55c1b71a858192bb9c289c07db05b455b11d617bb43701d9bb8338b470000902b696251a0ca57f7d586672f302b9fec7d607d1d21f1c3facb6168e34a2d6d010071b53be475bec52ec3caaa6d46c8fa0076d3fedc9931fc68128158a62590146400000541fece7f41b553fc4d5ea741a5f4a0fece0c866fdcbd6559feaf19ef69de9e5e0100e60941da8114ac11b14dbd429a6e1a71303cae4c111859259031f2a2d688c2b600000f0b9cfbff6da35e42392ddbf3e19151fc9d48141eaafb2e36de7911755e2f7f000062c20003ff099fb105fe67f92745be3f52371fdeea3a90b608db38a087ec63db0000020fa71272340d563844c620d4ee9677e054363b3f888a1ca1ca9b6a03908b8200002362f2b3ec34fdc8a65022ff3430426fb4137ea80e82cbe4f9a91f2a2f67557000f32bd21536b15b4ebc1175dff6b42058aaced347a983527a65e6f0db18b3553f8af704be0a336843e7b4892f0bdd26664ae5421db4d4c1db89bb6c704af0b7888018981ab9005af52dbea8352a3096ffecd60d5570eea0173f3b70d1d9d4869cdb99cb01fb0ecefe7ab0c08b9ebd608930544a9e5f849b876565d18cf1a2b2721251e679e8d3adf759e2374048624fb095e5aa8ef88f816d47879b6b45042670792db2b9fc1941b76d23c8f3a95249f144ac0972ff035940e55afcfd17d54bd9eda6da5736f24c2102961185bd6b96077d8084282d1cca465529d90e918ce9e6c8d0c2558a93feddd77c0e5c40d36b3dc19037ad1a5c6c35d1fa1e8ac8105ed44073c624d0edfb5335e5089341eac21390deb98da1a1c7860a8399f6a85a68ebc19aef63c88b1addac03c9f384366c4317256f206cdd884a5094cd6494c663f7f1f330cf7ad1ebd1e8c5726b9b7157cc65e8f7e7eabaf36a84c4226f7b5b58fcfa407585a7b2a08641ab9383e503c4cc335c8f61d847a587980cf962b71bff90d3f21012f11b71a2a378ff85684c1b7ba91c200d2a071b9c2a8c6308d963786b94c6ce10693e32a30dc216ae8a4371fd794d5a300bc9654465e1651e0201b975d65ba742ded0596fed0bb13285297e8d0d4a836dafee0ecbdc1de574e56db4de153c218824800287e9756f9c4424303ed58de7b0113b1423be543adac7845a1f87d54174a552cdd2184b3ce0cdbc9c56991b908996ec165a9c09455e3cc1a59a21cb57fa56e8f4792a9195b830aa3ff46db51f462ec7a8ac0daa3f32d02bcec6b1f831d2ad521cd3d1f5e03ae606b0d78187ec03156cfcccc2be0a2c9d459bf239fbf5fb7fbd62675ee8b5af93db9731f9558d57941bdd2077288878d6a98b2647e40f74030a78bff8900575e0e67401228c326f270e18d38886116e2fdd98fe94cab6e9fb8bcd71f43816ce6a3ab1fff8982f837eedde7b90e52fe0b84c012c5b69e37d5d60ea21121bef8b9d55e9d348d8ce978c84d29790c3b71f13ed84d90b9e24afb7a6a2cc7c1894d39a0096586bcbae23930c89550748bcaebef880c4fd2721eaa1b65a81effbbbbacfcaff2b7a1ea90426d5440486836418ff5c0d70de633649ffd18d8c630bc7dff4b3c0f1a2ec1477c922f33f13fa8a791d775c4d058477a1d93ff450f2755755d1b8a6cd7a3b5dd331bb6f6bb82e67706d3116be61bf16b780f8dd7f440a7d1060c6703f86bbb805abf0b3c4d9eb9cec54f9695b77349e8af87c416b4debd2e71e441d2a4bb9bce36886d16a58f63f2a269eb974c0cbe0d65b0fc124bb2048c8abe7ffce5a56aa3d2b89d4330cad59d02d68f64d93e7df725aed8bfce8e605518de7b6ed8ca73b033d1fc7e0128bb441b3b966b4c13692c5af8e5b4c6b025d946a84061f48f4b8e14ad10401a35c601f2e3f14011225c16966078bdcbe8c6a119590bc1597e4268342dbb68e99e3045643378d8d877787e810683094a64176757fa582253605a5239239290191ac12656779260eb4c253f1ce3d34abaa6d6b20a39f64cd8a3fa1dd204c49f03eb75276d1fb525eec02f0141eb07642adec56827d693fe1228cdfe38906e35678947895d42ebd0660a7701dcfa3d369b0a83a5bd623bc6466fa330e23637ab5e58d8875687cc6dac1a3af6db5f5693c14db711bf04815eb71a98ae816644fa046179dcfc9f2dad8000001840b294363c52bc923f16ba81481d75384e4aac3b63ce083f6e5f05c6564b1ea010001063966547b14e002a9eec62228b773d96a313e2c55332dcf37d77c1c7e8e57c9c77cd6b5acdb406870365bdf417e25f09bafc24e6cde5cadc959e2073beb3b7eeafa1241af4a1604f5222d99f84a745f4b65c3764c181e570dcfee722b7c6e5f10e5a78ab27b425ea96b5456fa6810f3ab14e3f38b33d36a7c71bc35ce9947950000dff5172c7f4eb4b7ec49f671a96498e36ff9ccacff113c8807821673916e873bb10502dac6bbcd52a5886bcd8cefbee20a6ad4547d1385600cd0175d95dd16850100323c56c93e60ccd3c5a8d38c5cbd35708c404f0ce77b8cdc9d3ef25147567d2a000039020f1c1812fd55c3705d7a6724b93b02836180d25abe93a0272d0aca54d70d0100f59352298577b96d82b2191d57ab7f9cc482f31786a1a5b2b3b03ba2a8aba335bd90ca453691709cbeb89babc0ba499b283dfb8bdb359fa441f1790963214fd1954a999a5003ebdacca67a1b1ae5f02212cec5f32f2d387ef07df030ba966e4f06084665afe690311e8e4371b00684576c76a8f358b86336b6ead26c75a27eac1f0100947638974fcc163b8dbbcb5fc749693d9e2c1101720f96db55fb1c3616d3cfe10100b8008130a9103eeecfe7a790d4bc681e23a31f34a2bdf4a80c99612f8c9336380100f04c88c626a3d7f33303989195856d45790c6d152ed089ad1a2f6d6147568c8d00006e69a1ddc3f140293ae476e4ea53e4c17b684d00fb46b6e22a4c3b844d63df49010035c50f4847ee31bede3c18be5a973cba4786b86c4364e1db0936c7379b142400010006df3558338a7da778adce88b71c13751ae2e490857723442b0bb4276a400748a3000023dc38540a0038f3f90e0315d5bb7f00df82e23e56f38d3ed499902bf6be419d0000e71592f7b77d73ff6fec168e668d78e782f5e85d0ab8ac0d40500a8daa4ba93c01006cc488cb587093c1f0f6d9d4bbc64b64fa2e0af38b6382ce5bbc9379bfc3a89300003a3223e8bc8aaf9a65a699edc8fe3eb05602c5ecdeab83a5b05b8fa28a187a780000994b553d1af8eef2283d262e3e7280661392201dc0241ab380421f48b14e27a90000
38 changes: 37 additions & 1 deletion mm2src/mm2_bitcoin/chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ pub struct Transaction {
/// https://github.com/navcoin/navcoin-core/blob/556250920fef9dc3eddd28996329ba316de5f909/src/primitives/transaction.h#L497
pub str_d_zeel: Option<String>,
pub tx_hash_algo: TxHashAlgo,
/// https://github.com/firoorg/firo/blob/bf7c7fa555bd00db970c5c3e64e7452ef20c800e/src/primitives/transaction.h#L389
pub v_extra_payload: Option<Vec<u8>>, // only available for special transaction types
}

impl From<&'static str> for Transaction {
Expand Down Expand Up @@ -417,6 +419,13 @@ impl Serializable for Transaction {
stream.append(&len);
stream.append_slice(string.as_bytes());
}
if let Some(ref payload) = self.v_extra_payload {
if !payload.is_empty() {
let len: CompactInteger = payload.len().into();
stream.append(&len);
stream.append_slice(payload);
}
}
},
true => {
stream
Expand Down Expand Up @@ -454,6 +463,10 @@ where
let overwintered: bool = (header >> 31) != 0;
let version = if overwintered { header & 0x7FFFFFFF } else { header };

// Check if we might have a version 3 (Spark) transaction using the 0xFFFF mask
let spark_version = header & 0xFFFF;
let maybe_spark = spark_version == 3;

let mut version_group_id = 0;
if overwintered {
version_group_id = reader.read()?;
Expand Down Expand Up @@ -489,7 +502,7 @@ where
let lock_time = reader.read()?;

let mut posv = false;
n_time = if tx_type == TxType::PosvWithNTime {
n_time = if tx_type == TxType::PosvWithNTime && !maybe_spark {
posv = true;
Some(reader.read()?)
} else {
Expand Down Expand Up @@ -545,6 +558,16 @@ where
None
};

// Check for extra payload if it might be a Spark transaction
let v_extra_payload = if maybe_spark && !reader.is_finished() {
let len: CompactInteger = reader.read()?;
let mut buf = vec![0; len.into()];
reader.read_slice(&mut buf)?;
Some(buf)
} else {
None
};

Ok(Transaction {
version,
n_time,
Expand All @@ -565,6 +588,7 @@ where
posv,
str_d_zeel,
tx_hash_algo: TxHashAlgo::DSHA256,
v_extra_payload,
})
}

Expand All @@ -580,6 +604,7 @@ impl Deserializable for Transaction {
// specific use case
let mut buffer = vec![];
reader.read_to_end(&mut buffer)?;

if let Ok(t) = deserialize_tx(&mut Reader::from_read(buffer.as_slice()), TxType::PosvWithNTime) {
return Ok(t);
}
Expand Down Expand Up @@ -877,6 +902,7 @@ mod tests {
posv: false,
str_d_zeel: None,
tx_hash_algo: TxHashAlgo::DSHA256,
v_extra_payload: None,
};
assert_eq!(actual, expected);
}
Expand Down Expand Up @@ -981,6 +1007,15 @@ mod tests {
assert_eq!(serialize(&t).to_hex::<String>(), transaction);
}

#[test]
fn firo_spark() {
let transaction =
include_str!("for_tests/firo_c50e5a3f16744ac86bacae28d9251a29bf754d250592bce16a953cd961b584d5");
Comment thread
levoncrypto marked this conversation as resolved.
let t: Transaction = transaction.into();
assert_eq!(2, t.outputs.len());
assert_eq!(serialize(&t).to_hex::<String>(), transaction);
}

#[test]
// https://kmdexplorer.io/tx/687acd73ad23ce93e7ddabeece8eb228a0a0e15e4d265f7c717d7458ddce9bdd
fn kmd_687acd73ad23ce93e7ddabeece8eb228a0a0e15e4d265f7c717d7458ddce9bdd() {
Expand Down Expand Up @@ -1063,6 +1098,7 @@ mod tests {
posv: false,
str_d_zeel: None,
tx_hash_algo: TxHashAlgo::DSHA256,
v_extra_payload: None,
};
assert_eq!(actual, expected);
}
Expand Down
3 changes: 3 additions & 0 deletions mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum ScriptType {
Call,
Create,
LelantusMint,
SparkMint,
ColdStaking,
// Komodo smart chains specific
CryptoCondition,
Expand Down Expand Up @@ -64,6 +65,7 @@ impl Serialize for ScriptType {
ScriptType::Call => "call".serialize(serializer),
ScriptType::Create => "create".serialize(serializer),
ScriptType::LelantusMint => "lelantusmint".serialize(serializer),
ScriptType::SparkMint => "sparksmint".serialize(serializer),
ScriptType::ColdStaking => "cold_staking".serialize(serializer),
ScriptType::CryptoCondition => "cryptocondition".serialize(serializer),
}
Expand Down Expand Up @@ -102,6 +104,7 @@ impl<'a> Deserialize<'a> for ScriptType {
"call" => Ok(ScriptType::Call),
"create" => Ok(ScriptType::Create),
"lelantusmint" => Ok(ScriptType::LelantusMint),
"sparksmint" => Ok(ScriptType::SparkMint),
"cold_staking" => Ok(ScriptType::ColdStaking),
"cryptocondition" => Ok(ScriptType::CryptoCondition),
_ => Err(E::invalid_value(Unexpected::Str(value), &self)),
Expand Down
13 changes: 13 additions & 0 deletions mm2src/mm2_bitcoin/rpc/src/v1/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ pub enum TransactionInputEnum {
Sigma(SigmaInput),
/// FIRO specific
Lelantus(LelantusInput),
/// FIRO specific
Spark(SparkInput),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
Expand All @@ -99,6 +101,17 @@ pub struct LelantusInput {
sequence: u32,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct SparkInput {
#[serde(rename = "scriptSig")]
pub script_sig: TransactionInputScript,
#[serde(rename = "nFees")]
pub n_fees: f64,
#[serde(rename = "lTags")]
l_tags: Vec<String>,
sequence: u32,
}

impl TransactionInputEnum {
pub fn is_coinbase(&self) -> bool { matches!(self, TransactionInputEnum::Coinbase(_)) }
}
Expand Down
6 changes: 6 additions & 0 deletions mm2src/mm2_bitcoin/script/src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub struct TransactionInputSigner {
pub posv: bool,
pub str_d_zeel: Option<String>,
pub hash_algo: SignerHashAlgo,
pub v_extra_payload: Option<Vec<u8>>,
}

/// Used for resigning and loading test transactions
Expand All @@ -186,6 +187,7 @@ impl From<Transaction> for TransactionInputSigner {
posv: t.posv,
str_d_zeel: t.str_d_zeel,
hash_algo: t.tx_hash_algo.into(),
v_extra_payload: t.v_extra_payload,
}
}
}
Expand Down Expand Up @@ -223,6 +225,7 @@ impl From<TransactionInputSigner> for Transaction {
join_split_sig: H512::default(),
str_d_zeel: t.str_d_zeel,
tx_hash_algo: t.hash_algo.into(),
v_extra_payload: t.v_extra_payload,
}
}
}
Expand Down Expand Up @@ -371,6 +374,7 @@ impl TransactionInputSigner {
posv: self.posv,
str_d_zeel: self.str_d_zeel.clone(),
tx_hash_algo: self.hash_algo.into(),
v_extra_payload: None,
};

let mut stream = Stream::default();
Expand Down Expand Up @@ -691,6 +695,7 @@ mod tests {
posv: false,
str_d_zeel: None,
hash_algo: SignerHashAlgo::DSHA256,
v_extra_payload: None,
};

let hash = input_signer.signature_hash(0, 0, &previous_output, SignatureVersion::Base, SighashBase::All.into());
Expand Down Expand Up @@ -743,6 +748,7 @@ mod tests {
posv: true,
str_d_zeel: None,
hash_algo: SignerHashAlgo::DSHA256,
v_extra_payload: None,
};

let hash = input_signer.signature_hash(0, 0, &previous_output, SignatureVersion::Base, SighashBase::All.into());
Expand Down