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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ __pycache__/
ledger/
# Build directory
build/

# VSCode
.vscode/
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ edition = "2021"

[dependencies]
messages = { path = "./messages" }
ledger_device_sdk = "1.34.0"
ledger_secure_sdk_sys = "1.15.0"
ledger_device_sdk = "1.35.1"
ledger_secure_sdk_sys = "1.16.1"
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
bech32 = { version = "0.11", default-features = false, features = ["alloc"] }
chrono = { version = "0.4", default-features = false, features = ["alloc"] }
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,7 @@ The following workflows are executed in [GitHub Actions](https://github.com/feat
- Various lint checks :
- Source code lint checks with `cargo fmt`
- Python functional test code lint checks with `pylint` and `mypy`

## Additional documentation

For development guidelines related to the app's memory usage see [docs/memory_usage.md](docs/memory_usage.md).
94 changes: 94 additions & 0 deletions docs/memory_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Memory usage

## Device memory size

Judging by `.ld` files in [the Ledger Rust SDK](https://github.com/LedgerHQ/ledger-device-rust-sdk/tree/cad196841dbd72c037cfa01bec81a4a3ae57a04e/ledger_secure_sdk_sys/devices),
the amount of SRAM each model has is:
| Device | SRAM |
| ----------------- | ---- |
| apex_p, nanosplus | 40KB |
| flex, stax | 36KB |
| nanox | 28KB |

The first part of the RAM will be occupied by the app's globals (one of which will be the heap used by the Rust code) and the rest is stack.

The `HEAP_SIZE` variable in `.cargo/config.toml` specifies the size of the Rust heap (which is just [a static array under the hood](https://github.com/LedgerHQ/ledger-device-rust-sdk/blob/cad196841dbd72c037cfa01bec81a4a3ae57a04e/ledger_secure_sdk_sys/src/lib.rs#L64)).

I.e. the bigger `HEAP_SIZE` is, the less is the stack. And with the `HEAP_SIZE` of 16KB, we'll have less than 12KB of stack at nanox.

## Reducing app stack usage

- Function's parameters and the return value consume stack space (unless the object is small enough to be put into a register).
- Moving an object around inside the function body may increase stack consumption as well.

So,
- Box large types if you need to pass/return them by value.
- Avoid unboxing boxed large objects when passing them by value. E.g. even if a function only needs `LargeObj`,
pass `Box<LargeObj>` to it anyway (which would be discouraged by the "normal" best practices), because passing it
unboxed would increase the stack usage.\
This includes the case when a member function consumes `self` - declare it as `self: Box<Self>` instead.
- `sizeof` of 200 bytes is probably large enough. E.g. in the past boxing certain objects of roughly this size
decreased stack usage by roughly 1.3KB (which is more than 10% of all stack space available on nanox).

### Determining the current stack usage of the app

Build the app with `emit-stack-sizes`:
```
RUSTFLAGS="-Z emit-stack-sizes" cargo ledger build nanox
```
After that you can use `llvm-readobj` to obtain sizes of stack frames of each function:
```
llvm-readobj --stack-sizes --demangle target/nanox/release/mintlayer-app
```

You can also force `llvm-readobj` to emit json and use `jq` to sort the output by the stack size. E.g. the following
will print 20 functions with the biggest stack frame size:
```
llvm-readobj --stack-sizes --demangle --elf-output-style=JSON target/nanox/release/mintlayer-app | jq -r '.[].StackSizes | sort_by(.Entry.Size) | reverse | .[:20][] | .Entry | "\(.Size)\t\(.Functions | join(", "))"'
```

### Determining the actual available stack

At least in the current version of the SDK, the linker script emits symbols that
can be used to determine the actual stack size, e.g. via `llvm-readelf`:
```
llvm-readelf -s target/nanox/release/mintlayer-app | rg '_stack|_estack'
```
Example output:
```
1581: da7a425c 0 NOTYPE GLOBAL DEFAULT 6 app_stack_canary
1624: da7a7000 0 NOTYPE GLOBAL DEFAULT 6 _estack
1697: da7a4260 0 NOTYPE GLOBAL DEFAULT 6 _stack
```
Here `_estack` is the end of the stack area, `_stack` is the beginning of it and `app_stack_canary` is a 4-byte marker
placed just below `_stack` and used to detect stack overflows. The difference between `_estack` and `_stack` will be
the stack size, in this case it's da7a7000-da7a4260=2DA0 (11680 in decimal).

### Other notes

This code:
```
fn foo(x: &X) {
match x {
X::A => { /*do stuff*/ },
X::B => { /*do other stuff*/ },
}
}
```
may use more stack than:
```
fn foo(x: &X) {
match x {
X::A => stuff(),
X::B => other_stuff(),
}
}

#[inline(never)] fn stuff() { /*do stuff*/ }
#[inline(never)] fn other_stuff() { /*do other stuff*/ }
```
I.e. it seems that LLVM cannot always reuse stack slots between different branches of the `match`, and with bigger enums
and bigger stack usage in each branch the overhead becomes bigger as well. So, splitting a large `match` into separate
non-inlinable functions may be a way of reducing the app's stack usage, but this should probably be the last resort,
because if all large objects are boxed, the stack usage in each branch should be relatively small, which will make
the overhead relatively small as well.
4 changes: 3 additions & 1 deletion messages/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ edition = "2024"
#
# Fix reference: https://github.com/paritytech/parity-scale-codec/pull/751
# This fix should be included in releases after version 3.7.5.
# Note: normally we would enable the "chain-error" feature of parity-scale-codec to make decode errors
# more informative. But in the Ledger app we never examine or even print those errors, so enabling this
# feature would only increase the size of the binary and make the app use more stack during decoding.
parity-scale-codec = { git = "https://github.com/paritytech/parity-scale-codec.git", rev = "5021525697edc0661591ebc71392c48d950a10b0", default-features = false, features = [
"derive",
"chain-error",
] }

num_enum = { version = "0.7.5", default-features = false }
Expand Down
26 changes: 18 additions & 8 deletions messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// Required for using String, Vec, format!...
extern crate alloc;

use alloc::vec::Vec;
use alloc::{boxed::Box, vec::Vec};
use core::iter::ExactSizeIterator;

use derive_more::Display;
Expand Down Expand Up @@ -95,9 +95,9 @@ pub struct SignMessageReq {

#[derive(Encode, Decode)]
pub enum SignTxReq {
Input(TxInputReq),
InputCommitment(mlcp::SighashInputCommitment),
Output(TxOutputReq),
Input(Box<TxInputReq>),
InputCommitment(Box<mlcp::SighashInputCommitment>),
Output(Box<TxOutputReq>),
NextSignature,
}

Expand Down Expand Up @@ -402,16 +402,24 @@ pub enum StatusWord {
// Standard Ledger APDU Codes
#[display("Success")]
Ok = 0x9000,
#[display("Nothing received")]
NothingReceived = 0x6982,
#[display("User cancelled")]
Deny = 0x6985,
#[display("CLA not supported")]
ClaNotSupported = 0x6E00,
#[display("Wrong P1/P2 parameters")]
WrongP1P2 = 0x6B00,
#[display("Instruction not supported")]
InsNotSupported = 0x6D00,
InsNotSupported = 0x6E01,
#[display("Wrong P1/P2 parameters")]
WrongP1P2 = 0x6E02,
#[display("Wrong APDU length")]
WrongApduLength = 0x6700,
WrongApduLength = 0x6E03,
#[display("Unknown")]
Unknown = 0x6D00,
#[display("Panic")]
Panic = 0xE000,
#[display("Device locked")]
DeviceLocked = 0x5515,

// App Specific Errors (0xB...)
#[display("Transaction display failed")]
Expand Down Expand Up @@ -454,6 +462,8 @@ pub enum StatusWord {
MaxBufferLenExceeded = 0xB012,
#[display("Different input commitment hash")]
DifferentInputCommitmentHash = 0xB013,
#[display("Invalid Timestamp")]
InvalidTimestamp = 0xB014,

// Ecc Errors
#[display("ECC Carry")]
Expand Down
4 changes: 2 additions & 2 deletions src/app_ui/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn transaction_title(tx_type: &Option<TxType>) -> &'static str {
Some(TxType::Htlc) => "Sign create HTLC transaction",
Some(TxType::CreateDelegation) => "Sign create delegation transaction",
Some(TxType::DelegationStake) => "Sign stake delegation transaction",
Some(TxType::DelegationWithdrawl) => "Sign withdrawal delegation transaction",
Some(TxType::DelegationWithdrawal) => "Sign withdrawal delegation transaction",
Some(TxType::CreateStakePool) => "Sign create stake pool transaction",
Some(TxType::DecommissionStakePool) => "Sign decommission stake pool transaction",
Some(TxType::CreateNft) => "Sign create NFT transaction",
Expand Down Expand Up @@ -478,7 +478,7 @@ fn format_input(input: &InputCommand, coin: CoinType) -> Result<FormatedOutput,
if cfg!(any(target_os = "nanosplus", target_os = "nanox")) {
("Del Wdrwl", address_short)
} else {
("Delegation withdrawl", address_short)
("Delegation withdrawal", address_short)
}
}
},
Expand Down
15 changes: 15 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,18 @@ pub fn cx_err_to_status(e: CxError) -> StatusWord {
CxError::GenericError => StatusWord::EccGenericError,
}
}

pub fn sdk_err_to_status(e: ledger_device_sdk::io::StatusWords) -> StatusWord {
match e {
ledger_device_sdk::io::StatusWords::Ok => StatusWord::Ok,
ledger_device_sdk::io::StatusWords::BadCla => StatusWord::ClaNotSupported,
ledger_device_sdk::io::StatusWords::NothingReceived => StatusWord::NothingReceived,
ledger_device_sdk::io::StatusWords::BadIns => StatusWord::InsNotSupported,
ledger_device_sdk::io::StatusWords::BadP1P2 => StatusWord::WrongP1P2,
ledger_device_sdk::io::StatusWords::BadLen => StatusWord::WrongApduLength,
ledger_device_sdk::io::StatusWords::UserCancelled => StatusWord::Deny,
ledger_device_sdk::io::StatusWords::Unknown => StatusWord::Unknown,
ledger_device_sdk::io::StatusWords::Panic => StatusWord::Panic,
ledger_device_sdk::io::StatusWords::DeviceLocked => StatusWord::DeviceLocked,
}
}
Loading
Loading