Browse Source

fix branch id intest files

checkpoints
DenioD 5 years ago
parent
commit
f00ef348f0
  1. 30
      lib/src/lightclient.rs
  2. 232
      lib/src/lightwallet.rs
  3. 94
      lib/src/lightwallet/tests.rs
  4. 25
      mkrelease.sh

30
lib/src/lightclient.rs

@ -641,7 +641,8 @@ impl LightClient {
pub fn do_list_transactions(&self) -> JsonValue {
let wallet = self.wallet.read().unwrap();
// Create a list of TransactionItems
// Create a list of TransactionItems from wallet txns
let mut tx_list = wallet.txs.read().unwrap().iter()
.flat_map(| (_k, v) | {
let mut txns: Vec<JsonValue> = vec![];
@ -709,6 +710,33 @@ impl LightClient {
})
.collect::<Vec<JsonValue>>();
// Add in all mempool txns
tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| {
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
use std::convert::TryInto;
let amount: u64 = wtx.outgoing_metadata.iter().map(|om| om.value).sum::<u64>();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
// Collect outgoing metadata
let outgoing_json = wtx.outgoing_metadata.iter()
.map(|om|
object!{
"address" => om.address.clone(),
"value" => om.value,
"memo" => LightWallet::memo_str(&Some(om.memo.clone())),
}).collect::<Vec<JsonValue>>();
object! {
"block_height" => wtx.block,
"datetime" => wtx.datetime,
"txid" => format!("{}", wtx.txid),
"amount" => -1 * (fee + amount) as i64,
"unconfirmed" => true,
"outgoing_metadata" => outgoing_json,
}
}));
tx_list.sort_by( |a, b| if a["block_height"] == b["block_height"] {
a["txid"].as_str().cmp(&b["txid"].as_str())
} else {

232
lib/src/lightwallet.rs

@ -119,6 +119,10 @@ pub struct LightWallet {
blocks: Arc<RwLock<Vec<BlockData>>>,
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// Transactions that are only in the mempool, but haven't been confirmed yet.
// This is not stored to disk.
pub mempool_txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// The block at which this wallet was born. Rescans
// will start from here.
birthday: u64,
@ -199,20 +203,21 @@ impl LightWallet {
= LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0);
Ok(LightWallet {
encrypted: false,
unlocked: true,
enc_seed: [0u8; 48],
nonce: vec![],
seed: seed_bytes,
extsks: Arc::new(RwLock::new(vec![extsk])),
extfvks: Arc::new(RwLock::new(vec![extfvk])),
zaddress: Arc::new(RwLock::new(vec![address])),
tkeys: Arc::new(RwLock::new(vec![tpk])),
taddresses: Arc::new(RwLock::new(vec![taddr])),
blocks: Arc::new(RwLock::new(vec![])),
txs: Arc::new(RwLock::new(HashMap::new())),
config: config.clone(),
birthday: latest_block,
encrypted: false,
unlocked: true,
enc_seed: [0u8; 48],
nonce: vec![],
seed: seed_bytes,
extsks: Arc::new(RwLock::new(vec![extsk])),
extfvks: Arc::new(RwLock::new(vec![extfvk])),
zaddress: Arc::new(RwLock::new(vec![address])),
tkeys: Arc::new(RwLock::new(vec![tpk])),
taddresses: Arc::new(RwLock::new(vec![taddr])),
blocks: Arc::new(RwLock::new(vec![])),
txs: Arc::new(RwLock::new(HashMap::new())),
mempool_txs: Arc::new(RwLock::new(HashMap::new())),
config: config.clone(),
birthday: latest_block,
})
}
@ -297,19 +302,20 @@ impl LightWallet {
let birthday = reader.read_u64::<LittleEndian>()?;
Ok(LightWallet{
encrypted: encrypted,
unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked.
enc_seed: enc_seed,
nonce: nonce,
seed: seed_bytes,
extsks: Arc::new(RwLock::new(extsks)),
extfvks: Arc::new(RwLock::new(extfvks)),
zaddress: Arc::new(RwLock::new(addresses)),
tkeys: Arc::new(RwLock::new(tkeys)),
taddresses: Arc::new(RwLock::new(taddresses)),
blocks: Arc::new(RwLock::new(blocks)),
txs: Arc::new(RwLock::new(txs)),
config: config.clone(),
encrypted: encrypted,
unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked.
enc_seed: enc_seed,
nonce: nonce,
seed: seed_bytes,
extsks: Arc::new(RwLock::new(extsks)),
extfvks: Arc::new(RwLock::new(extfvks)),
zaddress: Arc::new(RwLock::new(addresses)),
tkeys: Arc::new(RwLock::new(tkeys)),
taddresses: Arc::new(RwLock::new(taddresses)),
blocks: Arc::new(RwLock::new(blocks)),
txs: Arc::new(RwLock::new(txs)),
mempool_txs: Arc::new(RwLock::new(HashMap::new())),
config: config.clone(),
birthday,
})
}
@ -470,6 +476,7 @@ impl LightWallet {
pub fn clear_blocks(&self) {
self.blocks.write().unwrap().clear();
self.txs.write().unwrap().clear();
self.mempool_txs.write().unwrap().clear();
}
pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool {
@ -1174,62 +1181,68 @@ impl LightWallet {
.map(|block| block.tree.clone())
.unwrap_or(CommitmentTree::new()),
};
// These are filled in inside the block
let new_txs;
let nfs: Vec<_>;
{
// Create a write lock
let mut txs = self.txs.write().unwrap();
// Create a write lock that will last for the rest of the function.
let mut txs = self.txs.write().unwrap();
// Create a Vec containing all unspent nullifiers.
// Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included
// during scan_block below.
let nfs: Vec<_> = txs
.iter()
.map(|(txid, tx)| {
let txid = *txid;
tx.notes.iter().filter_map(move |nd| {
if nd.spent.is_none() {
Some((nd.nullifier, nd.account, txid))
} else {
None
}
// Create a Vec containing all unspent nullifiers.
// Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included
// during scan_block below.
nfs = txs
.iter()
.map(|(txid, tx)| {
let txid = *txid;
tx.notes.iter().filter_map(move |nd| {
if nd.spent.is_none() {
Some((nd.nullifier, nd.account, txid))
} else {
None
}
})
})
})
.flatten()
.collect();
.flatten()
.collect();
// Prepare the note witnesses for updating
for tx in txs.values_mut() {
for nd in tx.notes.iter_mut() {
// Duplicate the most recent witness
if let Some(witness) = nd.witnesses.last() {
let clone = witness.clone();
nd.witnesses.push(clone);
// Prepare the note witnesses for updating
for tx in txs.values_mut() {
for nd in tx.notes.iter_mut() {
// Duplicate the most recent witness
if let Some(witness) = nd.witnesses.last() {
let clone = witness.clone();
nd.witnesses.push(clone);
}
// Trim the oldest witnesses
nd.witnesses = nd
.witnesses
.split_off(nd.witnesses.len().saturating_sub(100));
}
// Trim the oldest witnesses
nd.witnesses = nd
.witnesses
.split_off(nd.witnesses.len().saturating_sub(100));
}
}
let new_txs = {
let nf_refs: Vec<_> = nfs.iter().map(|(nf, acc, _)| (&nf[..], *acc)).collect();
// Create a single mutable slice of all the newly-added witnesses.
let mut witness_refs: Vec<_> = txs
.values_mut()
.map(|tx| tx.notes.iter_mut().filter_map(|nd| nd.witnesses.last_mut()))
.flatten()
.collect();
scan_block(
block.clone(),
&self.extfvks.read().unwrap(),
&nf_refs[..],
&mut block_data.tree,
&mut witness_refs[..],
)
};
new_txs = {
let nf_refs: Vec<_> = nfs.iter().map(|(nf, acc, _)| (&nf[..], *acc)).collect();
// Create a single mutable slice of all the newly-added witnesses.
let mut witness_refs: Vec<_> = txs
.values_mut()
.map(|tx| tx.notes.iter_mut().filter_map(|nd| nd.witnesses.last_mut()))
.flatten()
.collect();
scan_block(
block.clone(),
&self.extfvks.read().unwrap(),
&nf_refs[..],
&mut block_data.tree,
&mut witness_refs[..],
)
};
}
// If this block had any new Txs, return the list of ALL txids in this block,
// so the wallet can fetch them all as a decoy.
let all_txs = if !new_txs.is_empty() {
@ -1243,6 +1256,9 @@ impl LightWallet {
};
for tx in new_txs {
// Create a write lock
let mut txs = self.txs.write().unwrap();
// Mark notes as spent.
let mut total_shielded_value_spent: u64 = 0;
@ -1305,6 +1321,11 @@ impl LightWallet {
}
}
{
// Cleanup mempool tx after adding a block, to remove all txns that got mined
self.cleanup_mempool();
}
// Print info about the block every 10,000 blocks
if height % 10_000 == 0 {
match self.get_sapling_tree() {
@ -1350,7 +1371,7 @@ impl LightWallet {
);
// Convert address (str) to RecepientAddress and value to Amount
let tos = tos.iter().map(|to| {
let recepients = tos.iter().map(|to| {
let ra = match address::RecipientAddress::from_str(to.0,
self.config.hrp_sapling_address(),
self.config.base58_pubkey_address(),
@ -1485,7 +1506,7 @@ impl LightWallet {
// TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok?
let ovk = self.extfvks.read().unwrap()[0].fvk.ovk;
for (to, value, memo) in tos {
for (to, value, memo) in recepients {
// Compute memo if it exists
let encoded_memo = memo.map(|s| Memo::from_str(&s).unwrap());
@ -1541,11 +1562,66 @@ impl LightWallet {
}
}
// Add this Tx to the mempool structure
{
let mut mempool_txs = self.mempool_txs.write().unwrap();
match mempool_txs.get_mut(&tx.txid()) {
None => {
// Collect the outgoing metadata
let outgoing_metadata = tos.iter().map(|(addr, amt, maybe_memo)| {
OutgoingTxMetadata {
address: addr.to_string(),
value: *amt,
memo: match maybe_memo {
None => Memo::default(),
Some(s) => Memo::from_str(&s).unwrap(),
},
}
}).collect::<Vec<_>>();
// Create a new WalletTx
let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid());
wtx.outgoing_metadata = outgoing_metadata;
// Add it into the mempool
mempool_txs.insert(tx.txid(), wtx);
},
Some(_) => {
warn!("A newly created Tx was already in the mempool! How's that possible? Txid: {}", tx.txid());
}
}
}
// Return the encoded transaction, so the caller can send it.
let mut raw_tx = vec![];
tx.write(&mut raw_tx).unwrap();
Ok(raw_tx.into_boxed_slice())
}
// After some blocks have been mined, we need to remove the Txns from the mempool_tx structure
// if they :
// 1. Have expired
// 2. The Tx has been added to the wallet via a mined block
pub fn cleanup_mempool(&self) {
const DEFAULT_TX_EXPIRY_DELTA: i32 = 20;
let current_height = self.blocks.read().unwrap().last().map(|b| b.height).unwrap_or(0);
{
// Remove all expired Txns
self.mempool_txs.write().unwrap().retain( | _, wtx| {
current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA)
});
}
{
// Remove all txns where the txid is added to the wallet directly
self.mempool_txs.write().unwrap().retain ( |txid, _| {
self.txs.read().unwrap().get(txid).is_none()
});
}
}
}
#[cfg(test)]

94
lib/src/lightwallet/tests.rs

@ -690,7 +690,7 @@ fn get_test_wallet(amount: u64) -> (LightWallet, TxId, BlockHash) {
}
#[test]
fn test_z_spend() {
fn test_z_spend_to_z() {
const AMOUNT1: u64 = 50000;
let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1);
@ -703,7 +703,7 @@ fn test_z_spend() {
let outgoing_memo = "Outgoing Memo".to_string();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
@ -723,6 +723,19 @@ fn test_z_spend() {
assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid));
}
// It should also be in the mempool structure
{
let mem = wallet.mempool_txs.read().unwrap();
assert_eq!(mem[&sent_txid].block, 2); // block number is next block
assert! (mem[&sent_txid].datetime > 0);
assert_eq!(mem[&sent_txid].txid, sent_txid);
assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo);
}
let mut cb3 = FakeCompactBlock::new(2, block_hash);
cb3.add_tx(&sent_tx);
wallet.scan_block(&cb3.as_bytes()).unwrap();
@ -743,6 +756,12 @@ fn test_z_spend() {
assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None);
}
{
// And the mempool tx should disappear
let mem = wallet.mempool_txs.read().unwrap();
assert!(mem.get(&sent_txid).is_none());
}
// Now, full scan the Tx, which should populate the Outgoing Meta data
wallet.scan_full_tx(&sent_tx, 2, 0);
@ -771,7 +790,7 @@ fn test_multi_z() {
let outgoing_memo = "Outgoing Memo".to_string();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) =get_sapling_params().unwrap();
// Create a tx and send to address
@ -864,7 +883,7 @@ fn test_z_spend_to_taddr() {
const AMOUNT1: u64 = 50000;
let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1);
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
@ -959,7 +978,7 @@ fn test_t_spend_to_z() {
let outgoing_memo = "Outgoing Memo".to_string();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) =get_sapling_params().unwrap();
// Create a tx and send to address. This should consume both the UTXO and the note
@ -1033,7 +1052,7 @@ fn test_z_incoming_memo() {
let memo = "Incoming Memo".to_string();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
@ -1072,7 +1091,7 @@ fn test_z_to_t_withinwallet() {
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
@ -1132,7 +1151,7 @@ fn test_multi_t() {
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a Tx and send to the second t address
@ -1267,7 +1286,7 @@ fn test_multi_spends() {
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
let tos = vec![ (zaddr2.as_str(), ZAMOUNT2, Some(outgoing_memo2.clone())),
@ -1394,7 +1413,7 @@ fn test_bad_send() {
let (wallet, _txid1, _block_hash) = get_test_wallet(AMOUNT1);
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
@ -1426,7 +1445,7 @@ fn test_bad_params() {
let (wallet, _, _) = get_test_wallet(100000);
let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
// Bad params
let _ = wallet.send_to_address(branch_id, &[], &[],
vec![(&ext_taddr, 10, None)]);
@ -1448,6 +1467,53 @@ fn add_blocks(wallet: &LightWallet, start: i32, num: i32, mut prev_hash: BlockHa
Ok(new_blk.hash())
}
#[test]
fn test_z_mempool_expiry() {
const AMOUNT1: u64 = 50000;
let (wallet, _, block_hash) = get_test_wallet(AMOUNT1);
let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32]));
let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(),
&fvk.default_address().unwrap().1);
const AMOUNT_SENT: u64 = 20;
let outgoing_memo = "Outgoing Memo".to_string();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap();
let sent_tx = Transaction::read(&raw_tx[..]).unwrap();
let sent_txid = sent_tx.txid();
// It should also be in the mempool structure
{
let mem = wallet.mempool_txs.read().unwrap();
assert_eq!(mem[&sent_txid].block, 2); // block number is next block
assert! (mem[&sent_txid].datetime > 0);
assert_eq!(mem[&sent_txid].txid, sent_txid);
assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT);
assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo);
}
// Don't mine the Tx, but just add several blocks
add_blocks(&wallet, 2, 21, block_hash).unwrap();
// After 21 blocks, it should disappear (expiry is 20 blocks) since it was not mined
{
let mem = wallet.mempool_txs.read().unwrap();
assert!(mem.get(&sent_txid).is_none());
}
}
#[test]
fn test_block_limit() {
const AMOUNT: u64 = 500000;
@ -1529,7 +1595,7 @@ fn test_rollback() {
// Now do a Tx
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
@ -1795,7 +1861,7 @@ fn test_encrypted_zreceive() {
let outgoing_memo = "Outgoing Memo".to_string();
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
// Create a tx and send to address
@ -1901,7 +1967,7 @@ fn test_encrypted_treceive() {
let password: String = "password".to_string();
let (mut wallet, txid1, block_hash) = get_test_wallet(AMOUNT1);
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
let branch_id = u32::from_str_radix("76b809bb", 16).unwrap();
let (ss, so) = get_sapling_params().unwrap();
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());

25
mkrelease.sh

@ -43,7 +43,7 @@ mkdir -p target/macOS-silentdragonlite-cli-v$APP_VERSION
cp target/release/silentdragonlite-cli target/macOS-silentdragonlite-cli-v$APP_VERSION/
# For Windows and Linux, build via docker
docker run --rm -v $(pwd)/:/opt/silentdragonlite-light-cli rustbuild:latest bash -c "cd /opt/silentdragonlite-light-cli && cargo build --release && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu"
docker run --rm -v $(pwd)/:/opt/silentdragonlite-cli rustbuild:latest bash -c "cd /opt/silentdragonlite-cli && cargo build --release && cargo build --release --target armv7-unknown-linux-gnueabihf && cargo build --release --target aarch64-unknown-linux-gnu && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu"
# Now sign and zip the binaries
# macOS
@ -82,4 +82,27 @@ zip -r Windows-silentdragonlite-cli-v$APP_VERSION.zip Windows-silentdragonlite-c
cd ..
#Armv7
rm -rf target/Armv7-silentdragonlite-cli-v$APP_VERSION
mkdir -p target/Armv7-silentdragonlite-cli-v$APP_VERSION
cp target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli target/Armv7-silentdragonlite-cli-v$APP_VERSION/
gpg --batch --output target/Armv7-silentdragonlite-cli-v$APP_VERSION/silentdragonlite-cli.sig --detach-sig target/Armv7-silentdragonlite-cli-v$APP_VERSION/silentdragonlite-cli
cd target
cd Armv7-silentdragonlite-cli-v$APP_VERSION
gsha256sum silentdragonlite-cli > sha256sum.txt
cd ..
zip -r Armv7-silentdragonlite-cli-v$APP_VERSION.zip Armv7-silentdragonlite-cli-v$APP_VERSION
cd ..
#AARCH64
rm -rf target/aarch64-silentdragonlite-cli-v$APP_VERSION
mkdir -p target/aarch64-silentdragonlite-cli-v$APP_VERSION
cp target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli target/aarch64-silentdragonlite-cli-v$APP_VERSION/
gpg --batch --output target/aarch64-silentdragonlite-cli-v$APP_VERSION/silentdragonlite-cli.sig --detach-sig target/aarch64-silentdragonlite-cli-v$APP_VERSION/silentdragonlite-cli
cd target
cd aarch64-silentdragonlite-cli-v$APP_VERSION
gsha256sum silentdragonlite-cli > sha256sum.txt
cd ..
zip -r aarch64-silentdragonlite-cli-v$APP_VERSION.zip aarch64-silentdragonlite-cli-v$APP_VERSION
cd ..

Loading…
Cancel
Save