Skip to content

Commit 1c298de

Browse files
committed
assignment8
1 parent 57087ae commit 1c298de

20 files changed

+390
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[workspace]
2+
workspace.resolver = "1"
3+
members = ["rust"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Bitcoin Protocol Development - Week 8: Connecting to P2P network
2+
3+
## Overview
4+
In this challenge, you will interact with the Bitcoin P2P network. You will connect to a Bitcoin node, perform network handshakes, retrieve and parse block data, and extract relevant details from the parsed block.
5+
6+
## Objective
7+
The objective of this challenge is to:
8+
- **Establish a Connection to a Bitcoin Node:** You are required to discover and connect to a Bitcoin node. This will involve understanding the networking and communication protocols.
9+
- **Perform Version and Verack Handshake:** You are required to perform the initial handshake with the node, including sending and receiving version and verack messages, to establish a valid connection.
10+
- **Retrieve Block Data:** Using a known block hash obtained from a public blockchain explorer, you are required to request the block data from the connected node.
11+
- **Extract and Output Block Details**:
12+
- **Block Header:** 80 byte block header
13+
- **Block Hash:** 32 byte block hash
14+
- **Total Fees Collected:** total fees collected by the miner
15+
- **Miner Info:** Information about the miner who mined the block
16+
17+
The output of your script should be a file named `out.txt` that follows a specific format.
18+
19+
Place your solution in the appropriate directory based on your chosen language:
20+
- [bash](./bash/solution.sh)
21+
- [javascript](./javascript/index.js)
22+
- [python](./python/main.py)
23+
- [rust](./rust/src/main.rs)
24+
25+
## Requirements
26+
### Input
27+
- You have to complete this challenge for block at the height of 840000.
28+
29+
### Output
30+
Your script must generate an output file named `out.txt` with the following structure:
31+
- First line: The block header.
32+
- Second line: The block hash.
33+
- Third line: Total fee collected in sats
34+
- Fourth line: Miner Information
35+
36+
## Execution
37+
To test your solution locally:
38+
- Uncomment the line corresponding to your language in [run.sh](./run.sh).
39+
- Execute [`local.sh`](./local.sh).
40+
41+
If your code works, you will see the test completed successfully.
42+
43+
## Evaluation Criteria
44+
Your submission will be evaluated based on:
45+
- **Autograder**: Your code must pass the autograder [test script](./test/sanity-checks.spec.ts).
46+
- **Explainer Comments**: Include comments explaining each step of your code.
47+
- **Code Quality**: Your code should be well-organized, commented, and adhere to best practices.
48+
49+
### Plagiarism Policy
50+
Our plagiarism detection checker thoroughly identifies any instances of copying or cheating. Participants are required to publish their solutions in the designated repository, which is private and accessible only to the individual and the administrator. Solutions should not be shared publicly or with peers. In case of plagiarism, both parties involved will be directly disqualified to maintain fairness and integrity.
51+
52+
### AI Usage Disclaimer
53+
You may use AI tools like ChatGPT to gather information and explore alternative approaches, but avoid relying solely on AI for complete solutions. Verify and validate any insights obtained and maintain a balance between AI assistance and independent problem-solving.
54+
55+
## Why These Restrictions?
56+
These rules are designed to enhance your understanding of the technical aspects of Bitcoin. By completing this assignment, you gain practical experience with the technology that secures and maintains the trustlessness of Bitcoin. This challenge not only tests your ability to develop functional Bitcoin applications but also encourages deep engagement with the core elements of Bitcoin technology.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Tips on writing with rust
2+
3+
1. Attend the lecture or watch the recording on how to use tokio.
4+
1. make sure to await on the futures.
5+
1. Divide the tasks into small helper functions for easier debug.
6+
1. Test on regtest first with `-degug=net` option set, this will help you debug errors in your message serialization.
7+
1. Check the endianness of the messages
8+
1. Use `tracing::info or error` for logging. Don't use `dbg` or `println` as it won't give readable logs in multi threaded environment as we saw in last week.
9+
10+
## How to use TcpStream
11+
12+
It is like an `Iterator` except that not all the data are present in the beginning.
13+
It is a `Future` in the essence that you have to keep polling using `read` method to look for data.
14+
Use network magic bytes to mark the start of the message you received from the peer.
15+
Use `bitcoin::consensus::{serialize, deserialize}` to send and receive messages in your rust code.
16+
17+
18+
## Summary
19+
20+
I have added some code structure and pointers to make it easier for you.
21+
You can add more functions and unit tests.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bash/solution.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Write your solution here
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function main() {
2+
// Write your code here.
3+
}
4+
5+
main();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node ./javascript/index.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Config } from '@jest/types';
2+
3+
const config: Config.InitialOptions = {
4+
preset: 'ts-jest',
5+
verbose: true,
6+
moduleFileExtensions: ['js', 'json', 'ts'],
7+
rootDir: '.',
8+
testRegex: '.*\\.spec\\.ts$',
9+
transform: {
10+
'^.+\\.(t|j)s$': 'ts-jest',
11+
},
12+
testEnvironment: 'node',
13+
};
14+
export default config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
npm install
2+
3+
# Provide execution permission.
4+
chmod +x ./bash/run-bash.sh
5+
chmod +x ./python/run-python.sh
6+
chmod +x ./javascript/run-javascript.sh
7+
chmod +x ./rust/run-rust.sh
8+
chmod +x ./run.sh
9+
10+
# Run the test scripts
11+
/bin/bash run.sh
12+
npm run test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
00e05f2aab948491071265ad552351d0ad625745668da54b0172010000000000000000004f89a5d73bd4d4887f25981fe81892ccafda10c27f52d6f3dd28183a7c411b03b7072366194203177d9863ea
2+
0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "bpd-week-4-assignment",
3+
"version": "0.0.0",
4+
"description": "",
5+
"scripts": {
6+
"test": "jest"
7+
},
8+
"dependencies": {},
9+
"devDependencies": {
10+
"@types/jest": "29.5.0",
11+
"@types/node": "18.15.11",
12+
"jest": "29.5.0",
13+
"ts-jest": "29.0.5",
14+
"ts-loader": "^9.2.3",
15+
"ts-node": "^10.0.0",
16+
"tsconfig-paths": "4.2.0",
17+
"typescript": "^4.7.4"
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def main():
2+
# Write your code here
3+
4+
5+
if __name__ == "__main__":
6+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python ./python/main.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# if you are using bash, uncomment the line below
2+
# /bin/bash ./bash/run-bash.sh
3+
4+
# if you are using javascript, uncomment the line below
5+
# /bin/bash ./javascript/run-javascript.sh
6+
7+
# if you are using python, uncomment the line below
8+
# /bin/bash ./python/run-python.sh
9+
10+
# if you are using rust, uncomment the line below
11+
# /bin/bash ./rust/run-rust.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "rust"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
tokio = { version = "1", features = ["full"] }
10+
dns-lookup = "2.0.4"
11+
rand = "0.8.5"
12+
tracing = "0.1.40"
13+
tracing-subscriber = "0.3.18"
14+
bitcoin = { version = "0.32", features = ["bitcoinconsensus"] }
15+
hex = "0.4.3"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cargo run
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use dns_lookup::lookup_host;
2+
use std::{
3+
fs::File,
4+
io::{self, Error, ErrorKind, Write},
5+
net::{IpAddr, Ipv6Addr},
6+
str::FromStr,
7+
};
8+
use tokio::{
9+
io::{AsyncReadExt, AsyncWriteExt},
10+
net::TcpStream,
11+
};
12+
13+
use bitcoin::{
14+
consensus::{
15+
deserialize_partial,
16+
encode::{serialize_hex},
17+
},
18+
Block,
19+
};
20+
use bitcoin::hashes::{sha256d, Hash};
21+
use rand::seq::SliceRandom;
22+
use rand::thread_rng;
23+
use tokio::time::{timeout, Duration};
24+
25+
const PROTOCOL_VERSION: i32 = 70015;
26+
27+
// Define the fields
28+
struct NetAddress {
29+
}
30+
31+
// Define the fields
32+
struct VersionMessage {
33+
}
34+
35+
const REGTEST_MAGIC: [u8; 4] = [0;4];// todo!();
36+
const MAINNET_MAGIC: [u8; 4] = [0;4];// todo!();
37+
38+
fn serialize_net_address(addr: &NetAddress) -> Vec<u8> {
39+
todo!()
40+
}
41+
42+
fn serialize_version_msg(msg: &VersionMessage) -> Vec<u8> {
43+
todo!()
44+
}
45+
46+
fn hex_to_bytes(hex_string: &str) -> Result<Vec<u8>, std::num::ParseIntError> {
47+
// refer [hex to bytes conversion](https://github.com/bitcoin-dev-project/rust-for-bitcoiners/blob/main/tutorials/de_se_rialization/hex_bytes_conversions.md)
48+
todo!()
49+
}
50+
51+
fn request_block_message(hash: &str) -> Vec<u8> {
52+
todo!("Given a block hash return the block message inventory for getdata command")
53+
}
54+
55+
fn create_message(command: &str, payload: &[u8]) -> Vec<u8> {
56+
todo!("Given a command and payload prepare the message according to Message Structure section")
57+
}
58+
59+
fn is_verack(data: &[u8]) -> bool {
60+
todo!("Check whether these bytes starts with a verack message")
61+
}
62+
63+
fn randomize_slice<'a>(input: &'a [&'a str]) -> Vec<&'a str> {
64+
let mut rng = thread_rng();
65+
let mut vec = input.to_vec(); // Convert slice to vector
66+
vec.shuffle(&mut rng); // Shuffle the vector
67+
vec // Return the vector (or convert to a slice if needed)
68+
}
69+
70+
async fn get_valid_ip() -> Result<(TcpStream, IpAddr), String> {
71+
const DNS_SEEDS: [&str; 4] = [
72+
"seed.bitcoin.sipa.be",
73+
"dnsseed.bluematt.me",
74+
"dnsseed.bitcoin.dashjr.org",
75+
"seed.bitcoinstats.com",
76+
];
77+
todo!("Initially test with regtest with debug=net option");
78+
todo!("then test with your local full node");
79+
todo!("Then choose an ip from randomly iterating over DNS_SEEDS")
80+
}
81+
82+
// bitcoin messages did not end with any special character
83+
// So this function will keep reading from the stream until the read results in an error or 0
84+
async fn till_read_succeeds(stream: &mut TcpStream, buffer: &mut Vec<u8>) {
85+
loop {
86+
let mut t = [0; 1024];
87+
if let Ok(n) = stream.read(&mut t).await {
88+
if n == 0 {
89+
return;
90+
}
91+
buffer.extend(t);
92+
tracing::info!("read {n} bytes");
93+
} else {
94+
tracing::error!("Error in read");
95+
return;
96+
}
97+
}
98+
}
99+
100+
#[tokio::main]
101+
async fn main() -> io::Result<()> {
102+
let subscriber = tracing_subscriber::fmt()
103+
// Use a more compact, abbreviated log format
104+
.compact()
105+
// Display source code file paths
106+
.with_file(true)
107+
// Display source code line numbers
108+
.with_line_number(true)
109+
// Display the thread ID an event was recorded on
110+
.with_thread_ids(false)
111+
// Don't display the event's target (module path)
112+
.with_target(false)
113+
// Build the subscriber
114+
.finish();
115+
tracing::subscriber::set_global_default(subscriber).unwrap();
116+
let (mut stream, ip) = get_valid_ip().await.unwrap();
117+
118+
let ip6 = match ip {
119+
IpAddr::V4(addr) => addr.to_ipv6_mapped(),
120+
IpAddr::V6(addr) => addr,
121+
};
122+
123+
// Construct and send the version message
124+
let version_msg = todo!("prepare version message");
125+
126+
127+
Ok(())
128+
}
129+
130+
// A bitcoin peer will be continuously sending you messages
131+
// At some point you need to pause reading them and process the messages
132+
// So this function reads till a specified timeout
133+
async fn get_data_with_timeout(mut stream: &mut TcpStream, mut buffer: &mut Vec<u8>) {
134+
let timeout_duration = Duration::from_secs(10);
135+
let _ = timeout(
136+
timeout_duration,
137+
till_read_succeeds(&mut stream, &mut buffer),
138+
)
139+
.await;
140+
}
141+
142+
fn get_block_payload(buffer: &[u8]) -> &[u8] {
143+
todo!("The bitcoin node will keep sending you messages like ping, inv etc.,");
144+
todo!("One of them will be your required block message");
145+
todo!("How will you identify that?")
146+
}
147+
148+
fn starts_with_magic(buffer: &[u8]) -> bool {
149+
todo!("check whether the buffer strts with magic network characters")
150+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# provide execution permission.
2+
chmod +x ./bash/run-bash.sh
3+
chmod +x ./python/run-python.sh
4+
chmod +x ./javascript/run-javascript.sh
5+
chmod +x ./rust/run-rust.sh
6+
chmod +x ./run.sh
7+
8+
# Run the test scripts
9+
/bin/bash run.sh
10+
npm run test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { readFileSync } from "fs";
2+
3+
describe('Evaluate Submission', () => {
4+
let header: string;
5+
let hash: string;
6+
let totalFee: number;
7+
let minerInfo: string;
8+
9+
beforeAll(() => {
10+
const data = readFileSync('out.txt', 'utf8').trim().split('\n');
11+
header = data[0];
12+
hash = data[1];
13+
totalFee = parseInt(data[2]);
14+
minerInfo = data[3];
15+
});
16+
17+
it('should be defined', () => {
18+
expect(header).toBeDefined();
19+
expect(hash).toBeDefined();
20+
expect(totalFee).toBeDefined();
21+
expect(minerInfo).toBeDefined();
22+
});
23+
24+
it('should be the header of block 840000', () => {
25+
expect(header).toBe('00e05f2aab948491071265ad552351d0ad625745668da54b0172010000000000000000004f89a5d73bd4d4887f25981fe81892ccafda10c27f52d6f3dd28183a7c411b03b7072366194203177d9863ea');
26+
});
27+
28+
it('should be the hash of block 840000', () => {
29+
expect(hash).toBe('0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5');
30+
});
31+
32+
it('should be the total fee', () => {
33+
expect(totalFee).toBe(3762561499);
34+
});
35+
36+
it('should be the miner info', () => {
37+
expect(minerInfo).toBe('ViaBTC/Mined by buzz120');
38+
});
39+
});

0 commit comments

Comments
 (0)