Skip to content

Commit c7cf42c

Browse files
authored
Merge pull request #2006 from GitGab19/fix-version-bumps
remove dev-dependencies from `extensions_sv2`
2 parents 9b5721e + d8f4d5e commit c7cf42c

File tree

4 files changed

+100
-204
lines changed

4 files changed

+100
-204
lines changed

.github/workflows/release-libs.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ jobs:
7878
run: |
7979
./scripts/release-libs.sh sv2/channels-sv2
8080
81-
# parsers_sv2 (depends on binary_sv2, framing_sv2, common_messages, mining, template_distribution, job_declaration)
81+
# extensions_sv2 (depends on binary_sv2)
82+
- name: Publish crate extensions_sv2
83+
run: |
84+
./scripts/release-libs.sh sv2/extensions-sv2
85+
86+
# parsers_sv2 (depends on binary_sv2, framing_sv2, common_messages, mining, template_distribution, job_declaration, extensions_sv2)
8287
- name: Publish crate parsers_sv2
8388
run: |
8489
./scripts/release-libs.sh sv2/parsers-sv2
@@ -92,13 +97,8 @@ jobs:
9297
- name: Publish crate stratum_translation
9398
run: |
9499
./scripts/release-libs.sh stratum-core/stratum-translation
95-
96-
# extensions_sv2 (depends on binary_sv2, codec_sv2, framing_sv2, mining_sv2)
97-
- name: Publish crate extensions_sv2
98-
run: |
99-
./scripts/release-libs.sh sv2/extensions-sv2
100100
101-
# handlers_sv2 (depends on parsers_sv2, binary_sv2, common_messages_sv2, mining_sv2, template_distribution_sv2, job_declaration_sv2)
101+
# handlers_sv2 (depends on parsers_sv2, binary_sv2, common_messages_sv2, mining_sv2, template_distribution_sv2, job_declaration_sv2, extensions_sv2)
102102
- name: Publish crate handlers_sv2
103103
run: |
104104
./scripts/release-libs.sh sv2/handlers-sv2

sv2/extensions-sv2/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,3 @@ keywords = ["stratum", "mining", "bitcoin", "protocol", "extensions"]
1313

1414
[dependencies]
1515
binary_sv2 = { path = "../binary-sv2", version = "^5.0.0" }
16-
17-
[dev-dependencies]
18-
parsers_sv2 = { path = "../parsers-sv2", version = "^0.2.0" }
19-
mining_sv2 = { path = "../subprotocols/mining", version = "^6.0.0" }

sv2/extensions-sv2/examples/extensions_negotiation.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// NOTE: This example is part of the workspace and relies on workspace dependencies.
2+
// To run: cargo run --example extensions_negotiation --package extensions_sv2 (from workspace root)
3+
//
4+
// This example demonstrates the Extensions Negotiation protocol (extension_type=0x0001)
5+
// which allows Stratum V2 endpoints to negotiate which optional extensions are supported
6+
// during connection setup.
7+
18
use binary_sv2::Seq064K;
29
use extensions_sv2::{RequestExtensions, RequestExtensionsError, RequestExtensionsSuccess};
310

Lines changed: 86 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,228 +1,121 @@
1-
use binary_sv2::Seq064K;
1+
// NOTE: This is a simplified example that demonstrates UserIdentity creation.
2+
// For full TLV encoding/decoding examples with parsers_sv2, see the parsers_sv2 crate tests
3+
// or run integration tests from the workspace root.
4+
//
5+
// To run: cargo run --example worker_specific_hashrate_tracking --package extensions_sv2
6+
27
use extensions_sv2::{
3-
RequestExtensions, RequestExtensionsSuccess, UserIdentity,
4-
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING, TLV_FIELD_TYPE_USER_IDENTITY,
8+
UserIdentity, EXTENSION_TYPE_WORKER_HASHRATE_TRACKING, TLV_FIELD_TYPE_USER_IDENTITY,
59
};
6-
use mining_sv2::SubmitSharesExtended;
7-
use parsers_sv2::{TlvField, TlvList, TLV_HEADER_SIZE};
810

911
fn main() {
10-
println!("=== Worker-Specific Hashrate Tracking Extension Example ===\n");
11-
12-
// Step 1: Client requests the Worker Hashrate Tracking extension
13-
println!("1. Extension Negotiation:");
14-
println!(" Client requests Worker Hashrate Tracking extension (0x0002)");
12+
println!("=== Worker-Specific Hashrate Tracking Extension (Simplified) ===\n");
1513

16-
let request = RequestExtensions {
17-
request_id: 1,
18-
requested_extensions: Seq064K::new(vec![EXTENSION_TYPE_WORKER_HASHRATE_TRACKING]).unwrap(),
19-
};
20-
println!(" {}", request);
14+
// Step 1: Extension Overview
15+
println!("1. Extension Overview:");
2116
println!(
22-
" Extension type: 0x{:04x}\n",
17+
" Extension Type: 0x{:04x}",
2318
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING
2419
);
25-
26-
// Step 2: Server accepts the extension
27-
println!("2. Server Response:");
28-
let success = RequestExtensionsSuccess {
29-
request_id: 1,
30-
supported_extensions: Seq064K::new(vec![EXTENSION_TYPE_WORKER_HASHRATE_TRACKING]).unwrap(),
31-
};
32-
println!(" {}", success);
20+
println!(" Purpose: Track individual worker hashrates within aggregated channels");
3321
println!(
34-
" Extension 0x{:04x} is now active!\n",
35-
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING
22+
" TLV Field Type for user_identity: 0x{:02x}\n",
23+
TLV_FIELD_TYPE_USER_IDENTITY
3624
);
3725

38-
// Step 3: Demonstrate TLV field for user_identity
39-
println!("3. TLV Field Structure:");
40-
println!(" When submitting shares, the client appends a TLV field:");
41-
println!(
42-
" - Type (U16 | U8): Extension Type (0x{:04x}) | Field Type (0x{:02x})",
43-
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING, TLV_FIELD_TYPE_USER_IDENTITY
44-
);
45-
println!(" - Length (U16): Actual byte length of worker name");
46-
println!(" - Value: UTF-8 encoded worker identifier\n");
47-
48-
// Step 4: Create UserIdentity TLV fields for different workers
49-
println!("4. Example TLV Fields for Different Workers:\n");
26+
// Step 2: Creating UserIdentity instances
27+
println!("2. Creating UserIdentity Instances:\n");
5028

51-
let workers = vec!["Worker_001", "Miner_A", "Device_12345678901234567890123"];
29+
let workers = vec![
30+
"Worker_001",
31+
"Miner_A",
32+
"Device_12345678901234567890123", // 27 bytes (under 32 byte limit)
33+
];
5234

5335
for worker_name in workers {
54-
let user_id = UserIdentity::new(worker_name).unwrap();
55-
56-
println!(" {}", user_id);
57-
let tlv_bytes = TlvField::to_bytes(&user_id).unwrap();
58-
println!(" TLV Size: {} bytes", tlv_bytes.len());
59-
println!(
60-
" TLV Format: [Type: 0x{:04x}|0x{:02x}] [Length: 0x{:04x}] [Value: \"{}\"]",
61-
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING,
62-
TLV_FIELD_TYPE_USER_IDENTITY,
63-
worker_name.len(),
64-
worker_name
65-
);
66-
67-
println!(
68-
" Total TLV overhead: {} bytes + {} bytes (worker name) = {} bytes\n",
69-
TLV_HEADER_SIZE,
70-
worker_name.len(),
71-
tlv_bytes.len()
72-
);
73-
}
74-
75-
// Step 5: Build a complete SubmitSharesExtended frame with TLV
76-
println!("5. Building a SubmitSharesExtended Frame with Worker Identity:\n");
77-
78-
let submit_shares = SubmitSharesExtended {
79-
channel_id: 1,
80-
sequence_number: 42,
81-
job_id: 1,
82-
nonce: 0x12345678,
83-
ntime: 0x87654321,
84-
version: 0x20000000,
85-
extranonce: vec![0x01, 0x02, 0x03, 0x04].try_into().unwrap(),
86-
};
87-
88-
// Without worker identity (empty TLV list)
89-
let tlv_list_empty = TlvList::from_slice(&[]).unwrap();
90-
let frame_bytes_without = tlv_list_empty
91-
.build_frame_bytes_with_tlvs(parsers_sv2::Mining::SubmitSharesExtended(
92-
submit_shares.clone(),
93-
))
94-
.unwrap();
95-
println!(
96-
" Frame bytes without worker identity: {} bytes",
97-
frame_bytes_without.len()
98-
);
99-
100-
// With worker identity (using UserIdentity::to_tlv())
101-
let user_identity = UserIdentity::new("Worker_001").unwrap();
102-
let tlv = user_identity.to_tlv().unwrap();
103-
let tlv_list_with = TlvList::from_slice(&[tlv]).unwrap();
104-
let frame_bytes_with = tlv_list_with
105-
.build_frame_bytes_with_tlvs(parsers_sv2::Mining::SubmitSharesExtended(submit_shares))
106-
.unwrap();
107-
println!(
108-
" Frame bytes with worker identity: {} bytes",
109-
frame_bytes_with.len()
110-
);
111-
println!(
112-
" Difference: {} bytes (TLV overhead + worker name)\n",
113-
frame_bytes_with.len() - frame_bytes_without.len()
114-
);
115-
116-
// Step 6: Parsing TLV fields on the receiving side (Pool)
117-
println!("6. Parsing TLV Fields from Received Messages:\n");
118-
println!(" When a pool receives SubmitSharesExtended with TLV data:");
119-
120-
// Simulate TLV data that would be appended to a message
121-
let worker1_tlv = TlvField::to_bytes(&UserIdentity::new("Worker_001").unwrap()).unwrap();
122-
let worker2_tlv = TlvField::to_bytes(&UserIdentity::new("Miner_A").unwrap()).unwrap();
123-
124-
// Demo 1: Parse single TLV
125-
println!("\n a) Parse single worker identity:");
126-
let tlv_list = TlvList::from_bytes(&worker1_tlv);
127-
let negotiated = vec![EXTENSION_TYPE_WORKER_HASHRATE_TRACKING];
128-
let tlvs = tlv_list.for_extensions(&negotiated);
129-
130-
// Find user_identity TLV
131-
for tlv in &tlvs {
132-
if tlv.r#type.extension_type == EXTENSION_TYPE_WORKER_HASHRATE_TRACKING
133-
&& tlv.r#type.field_type == TLV_FIELD_TYPE_USER_IDENTITY
134-
{
135-
if let Ok(user_identity) = UserIdentity::from_tlv(tlv) {
136-
println!(" Found worker: {}", user_identity);
36+
match UserIdentity::new(worker_name) {
37+
Ok(user_id) => {
38+
println!(" Created: {}", user_id);
39+
println!(" - Name: {}", user_id.as_str().unwrap());
40+
println!(" - Length: {} bytes", user_id.len());
41+
println!(" - Raw bytes: {:?}\n", user_id.as_bytes());
13742
}
138-
}
139-
}
140-
141-
// Demo 2: Parse with TlvList iterator API
142-
println!("\n b) Using iterator API to process TLVs:");
143-
for result in tlv_list.iter() {
144-
match result {
145-
Ok(tlv) => {
146-
println!(
147-
" - Extension: 0x{:04x}, Field: 0x{:02x}, Length: {} bytes",
148-
tlv.r#type.extension_type, tlv.r#type.field_type, tlv.length
149-
);
150-
if let Ok(s) = std::str::from_utf8(&tlv.value) {
151-
println!(" Value: \"{}\"", s);
152-
}
43+
Err(e) => {
44+
println!(" Error creating worker '{}': {}\n", worker_name, e);
15345
}
154-
Err(e) => println!(" - Error parsing TLV: {:?}", e),
15546
}
15647
}
15748

158-
// Demo 3: Multiple TLVs (simulating batch processing)
159-
println!("\n c) Processing multiple shares with different workers:");
160-
let shares_with_workers = vec![
161-
("Worker_001", worker1_tlv.clone()),
162-
("Miner_A", worker2_tlv.clone()),
163-
];
49+
// Step 3: Demonstrate length limits
50+
println!("3. Length Validation:\n");
16451

165-
for (expected, tlv_data) in shares_with_workers {
166-
let tlvs = TlvList::from_bytes(&tlv_data).for_extensions(&negotiated);
167-
for tlv in &tlvs {
168-
if tlv.r#type.extension_type == EXTENSION_TYPE_WORKER_HASHRATE_TRACKING
169-
&& tlv.r#type.field_type == TLV_FIELD_TYPE_USER_IDENTITY
170-
{
171-
if let Ok(worker) = UserIdentity::from_tlv(tlv) {
172-
println!(" Share from: {} (expected: {})", worker, expected);
173-
}
174-
}
52+
// Valid: exactly 32 bytes
53+
let max_length_name = "x".repeat(32);
54+
match UserIdentity::new(&max_length_name) {
55+
Ok(user_id) => {
56+
println!(" ✓ 32-byte worker name accepted (max length)");
57+
println!(" Length: {} bytes\n", user_id.len());
58+
}
59+
Err(e) => {
60+
println!(" ✗ Unexpected error: {}\n", e);
17561
}
17662
}
17763

178-
// Demo 4: Using find() to search for specific TLV
179-
println!("\n d) Find specific TLV field:");
180-
if let Some(tlv) = TlvList::from_bytes(&worker1_tlv).find(
181-
EXTENSION_TYPE_WORKER_HASHRATE_TRACKING,
182-
TLV_FIELD_TYPE_USER_IDENTITY,
183-
) {
184-
println!(
185-
" Found user_identity TLV with {} bytes",
186-
tlv.value.len()
187-
);
188-
if let Ok(s) = std::str::from_utf8(&tlv.value) {
189-
println!(" Worker: \"{}\"", s);
64+
// Invalid: exceeds 32 bytes
65+
let too_long_name = "x".repeat(33);
66+
match UserIdentity::new(&too_long_name) {
67+
Ok(_) => {
68+
println!(" ✗ 33-byte worker name should have been rejected!\n");
69+
}
70+
Err(e) => {
71+
println!(" ✓ 33-byte worker name rejected as expected");
72+
println!(" Error: {}\n", e);
19073
}
19174
}
19275

193-
println!();
76+
// Step 4: Empty worker identity
77+
println!("4. Edge Cases:\n");
19478

195-
// Step 7: Convenience methods for applications
196-
println!("7. Simplified API for Applications:\n");
79+
let empty_identity = UserIdentity::new("").unwrap();
80+
println!(" Empty identity:");
81+
println!(" - Is empty: {}", empty_identity.is_empty());
82+
println!(" - Length: {} bytes\n", empty_identity.len());
19783

198-
println!(" a) Convert UserIdentity to TLV:");
199-
let user_identity = UserIdentity::new("Worker_001").unwrap();
200-
let tlv = TlvField::to_tlv(&user_identity).unwrap();
201-
println!(
202-
" Created TLV: extension_type=0x{:04x}, field_type=0x{:02x}",
203-
tlv.r#type.extension_type, tlv.r#type.field_type
204-
);
84+
// Step 5: Byte-level construction
85+
println!("5. Creating from Raw Bytes:\n");
20586

206-
println!("\n b) Create Vec<Tlv> from UserIdentity:");
207-
let tlv_vec = vec![TlvField::to_tlv(&user_identity).unwrap()];
208-
println!(" Created Vec<Tlv> with {} element(s)", tlv_vec.len());
209-
println!(
210-
" Usage: channel_sender.send((Mining::SubmitSharesExtended(msg), Some(tlv_vec)))?;"
211-
);
87+
let raw_bytes = b"CustomWorker";
88+
let identity = UserIdentity::from_bytes(raw_bytes).unwrap();
89+
println!(" From bytes: {:?}", raw_bytes);
90+
println!(" Result: {}", identity);
91+
println!(" As string: {}\n", identity.as_str().unwrap());
21292

213-
println!("\n This simplifies the application code:");
214-
println!(" let user_identity = UserIdentity::new(worker_name)?;");
215-
println!(" let tlv = TlvField::to_tlv(&user_identity)?;");
216-
println!(" let tlv_vec = vec![tlv];");
93+
// Step 6: Display method
94+
println!("6. Display Format:\n");
21795

218-
println!();
96+
let identity = UserIdentity::new("ExampleWorker").unwrap();
97+
println!(" Display: {}", identity);
98+
println!(" Debug: {:?}\n", identity);
99+
100+
// Step 7: Invalid UTF-8 handling
101+
println!("7. Invalid UTF-8 Handling:\n");
102+
103+
let invalid_utf8_bytes = vec![0xFF, 0xFE, 0xFD];
104+
let invalid_identity = UserIdentity::from_bytes(&invalid_utf8_bytes).unwrap();
105+
println!(" Bytes: {:?}", invalid_utf8_bytes);
106+
println!(" as_str(): {:?}", invalid_identity.as_str());
107+
println!(
108+
" as_string_or_hex(): {}\n",
109+
invalid_identity.as_string_or_hex()
110+
);
219111

220-
// Step 9: Use case example
221-
println!("9. Use Case - Mining Farm with Multiple Workers:");
222-
println!(" A mining farm with 100 devices can use a single extended channel");
223-
println!(" and track per-device hashrate by including the device worker_name in each share.");
224-
println!(" This allows the pool to monitor individual device performance while");
225-
println!(" reducing connection overhead compared to 100 separate channels.\n");
112+
// Step 8: Usage context
113+
println!("8. Usage in Stratum V2:\n");
114+
println!(" When the Worker-Specific Hashrate Tracking extension (0x0002) is negotiated,");
115+
println!(" UserIdentity can be included as a TLV field in SubmitSharesExtended messages.");
116+
println!(" This allows pools to track per-worker hashrate within aggregated channels.\n");
117+
println!(" For full TLV encoding/decoding examples, use the parsers_sv2 crate which");
118+
println!(" implements the TlvField trait for UserIdentity.\n");
226119

227120
println!("=== Example Complete ===");
228121
}

0 commit comments

Comments
 (0)