From 84730135464df9720afc1ed1a1da1f2a098d5cb6 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Thu, 3 Oct 2019 22:01:32 +0200 Subject: [PATCH 1/8] Initial bee-pow RFC draft --- text/0000-bee-pow/0000-bee-pow.md | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 text/0000-bee-pow/0000-bee-pow.md diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md new file mode 100644 index 00000000..cf1a3d1f --- /dev/null +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -0,0 +1,54 @@ ++ Feature name: `bee-pow` ++ Start date: 2019-10-03 ++ RFC PR: [iotaledger/bee-rfcs#0000](https://github.com/iotaledger/bee-rfcs/pull/0000) ++ Bee issue: [iotaledger/bee#0000](https://github.com/iotaledger/bee/issues/0000) + +# Summary + +One paragraph explanation of the feature. + +# Motivation + +Why are we doing this? What use cases does it support? What is the expected +outcome? + +1. Write a summary of the motivation. +2. List all the specific use cases that your proposal is trying to address. +3. Where applicable, write from the perspective of the person who will be using + the software, for example using the "Job story" format: + +When ___ , I want to ___, so I can ___. + ++ **Example 1:** When I query a node for a list of transactions, I want to be + able to sort them by date, so I can work with the most relevant ones. ++ **Example 2:** When I configure a node, I want to be able to control how much + transaction history the node stores, so I can make sure I only store the data + I need without incurring additional operational costs. + +# Detailed design + +This is the bulk of the RFC. Explain the design in enough detail for somebody +familiar with the IOTA and to understand, and for somebody familiar with Rust +to implement. This should get into specifics and corner-cases, and include +examples of how the feature is used. + +# Drawbacks + +Why should we *not* do this? + +# Rationale and alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not + choosing them? +- What is the impact of not doing this? + +# Unresolved questions + +- What parts of the design do you expect to resolve through the RFC process + before this gets merged? +- What parts of the design do you expect to resolve through the implementation + of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be + addressed in the future independently of the solution that comes out of this + RFC? From 9d5b6d98b1f10697a0bb7d322885362fb34ea33d Mon Sep 17 00:00:00 2001 From: /alex/ Date: Thu, 3 Oct 2019 23:10:07 +0200 Subject: [PATCH 2/8] Background info on PoW --- text/0000-bee-pow/0000-bee-pow.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index cf1a3d1f..833d1575 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -5,10 +5,22 @@ # Summary -One paragraph explanation of the feature. +This RFC proposes a dedicated crate to perform proof-of-work (PoW) for a single gossip message, that achieves to be accepted and propagated by the network. # Motivation +In order to protect the network from spam each device that wishes to get a message (e.g. a value transaction) propagated by all the nodes needs a way to proove to the network, that it has invested the necessary amount of computational work. + +### Background info on PoW +Cryptographic hash functions are one-way-functions. That means in simple terms that given the output you cannot calculate the input from it. Proof-of-Work works by setting some constraint on the output like a certain number of zeros at the end or the beginning of the hash. It's completely arbitrary. It could be ten `1`s or the sequence `1234567890` because any particular sequence is equally likely. What matters is that your only chance to find an input that satisfies the constraint is guessing/brute-forcing it. You can also easily set the difficulty by making the constraint harder to satisfy (extend the sequence). So to check if PoW was correctly done all a validating node has to do is hash the given message which includes the nonce, and see if the constraint is satisfied. On the other hand the message publisher has to repeat the following cycle many times until he has found a valid nonce: +* Pick a (random) nonce value +* Hash the message together with selected nonce +* Compare if the hash satisfies the constraint given by the majority of the nodes + +This process is called `Mining` and in this analogy the nonce is the nugget. + +TODO + Why are we doing this? What use cases does it support? What is the expected outcome? From 4c0abe77c4bc8ca9dcadfa009a43ecf701f1619e Mon Sep 17 00:00:00 2001 From: /alex/ Date: Fri, 4 Oct 2019 00:04:43 +0200 Subject: [PATCH 3/8] Job stories --- text/0000-bee-pow/0000-bee-pow.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index 833d1575..7feff331 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -9,13 +9,13 @@ This RFC proposes a dedicated crate to perform proof-of-work (PoW) for a single # Motivation -In order to protect the network from spam each device that wishes to get a message (e.g. a value transaction) propagated by all the nodes needs a way to proove to the network, that it has invested the necessary amount of computational work. +In order to protect the network from spam each device that wishes to get a message (e.g. a value transaction) propagated by all the nodes needs a way to proove to the network, that it has invested the necessary amount of computational work either by doing the computation by itself, or by externalizing it using some PoW service provider. ### Background info on PoW -Cryptographic hash functions are one-way-functions. That means in simple terms that given the output you cannot calculate the input from it. Proof-of-Work works by setting some constraint on the output like a certain number of zeros at the end or the beginning of the hash. It's completely arbitrary. It could be ten `1`s or the sequence `1234567890` because any particular sequence is equally likely. What matters is that your only chance to find an input that satisfies the constraint is guessing/brute-forcing it. You can also easily set the difficulty by making the constraint harder to satisfy (extend the sequence). So to check if PoW was correctly done all a validating node has to do is hash the given message which includes the nonce, and see if the constraint is satisfied. On the other hand the message publisher has to repeat the following cycle many times until he has found a valid nonce: -* Pick a (random) nonce value +Cryptographic hash functions are one-way-functions. That means in simple terms that given the output you cannot calculate the input from it. Proof-of-Work works by setting some constraint on the output like a certain number of zeros at the end or the beginning of the hash. It's completely arbitrary. It could be ten `1`s or the sequence `1234567890` because any particular sequence is equally likely. What matters is that your only chance to find an input that satisfies the constraint is guessing/brute-forcing it. You can also easily set the difficulty by making the constraint harder to satisfy (extend the sequence). So to check if PoW was correctly done all a validating node has to do is hash the given message which includes the nonce, and see if the constraint is satisfied. On the other hand the message publisher has to repeat the following cycle many times until it has found a valid nonce: +* Pick a nonce value (e.g. by simply increasing it, or choose it randomly) * Hash the message together with selected nonce -* Compare if the hash satisfies the constraint given by the majority of the nodes +* Compare if the hash satisfies the constraint given by the majority of the network This process is called `Mining` and in this analogy the nonce is the nugget. @@ -26,16 +26,18 @@ outcome? 1. Write a summary of the motivation. 2. List all the specific use cases that your proposal is trying to address. -3. Where applicable, write from the perspective of the person who will be using the software, for example using the "Job story" format: -When ___ , I want to ___, so I can ___. +TODO + +### Job stories: +When I create a gossip message (e.g. a transaction), I want to be able to find a nonce, so that hashing the message and the nonce creates a hash that satisfies the constraint set by the network. + +When I receive a gossip message, I want to be able to verify, that the contained nonce is valid, so that I know that the publisher of that message has at least invested some resource to find it. + -+ **Example 1:** When I query a node for a list of transactions, I want to be - able to sort them by date, so I can work with the most relevant ones. -+ **Example 2:** When I configure a node, I want to be able to control how much - transaction history the node stores, so I can make sure I only store the data - I need without incurring additional operational costs. +`IMPORTANT NOTE` +It is important that this crate is independent from the particular hashfunction. Ideally it would not even depend on whether a trinary or a binary hashfunction is used. In both cases a # Detailed design From 409ce0f44b9f3789ad16f921aeaa5727c114ed25 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Fri, 4 Oct 2019 00:17:13 +0200 Subject: [PATCH 4/8] Added link to preview repo --- text/0000-bee-pow/0000-bee-pow.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index 7feff331..48af86d2 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -5,7 +5,7 @@ # Summary -This RFC proposes a dedicated crate to perform proof-of-work (PoW) for a single gossip message, that achieves to be accepted and propagated by the network. +This RFC proposes a dedicated crate to perform proof-of-work (PoW) for a single gossip message, that attempts to become accepted and propagated by the network. # Motivation @@ -37,14 +37,16 @@ When I receive a gossip message, I want to be able to verify, that the contained `IMPORTANT NOTE` -It is important that this crate is independent from the particular hashfunction. Ideally it would not even depend on whether a trinary or a binary hashfunction is used. In both cases a +It is important that this crate is independent from the particular hashfunction. Ideally it would not even depend on whether a trinary or a binary hashfunction is used. # Detailed design -This is the bulk of the RFC. Explain the design in enough detail for somebody +`This is the bulk of the RFC. Explain the design in enough detail for somebody familiar with the IOTA and to understand, and for somebody familiar with Rust to implement. This should get into specifics and corner-cases, and include -examples of how the feature is used. +examples of how the feature is used.` + +For a prototype implementation please checkout [bee-pow-preview](https://github.com/Alex6323/bee-pow-preview). # Drawbacks From 9543997631c6a11ef390e047da1af2848a00ac53 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Tue, 15 Oct 2019 14:55:22 +0200 Subject: [PATCH 5/8] Wrote and/or reworked all paragraphs --- text/0000-bee-pow/0000-bee-pow.md | 81 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index 48af86d2..d5625758 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -1,70 +1,67 @@ -+ Feature name: `bee-pow` ++ Feature name: `bee-pow` + Start date: 2019-10-03 -+ RFC PR: [iotaledger/bee-rfcs#0000](https://github.com/iotaledger/bee-rfcs/pull/0000) ++ RFC PR: [iotaledger/bee-rfcs#0000](https://github.com/iotaledger/bee-rfcs/pull/0000) + Bee issue: [iotaledger/bee#0000](https://github.com/iotaledger/bee/issues/0000) # Summary -This RFC proposes a dedicated crate to perform proof-of-work (PoW) for a single gossip message, that attempts to become accepted and propagated by the network. +IOTA is a permissionless and feeless network which unavoidably makes it vulnerable to spam attacks. To make such +attacks more costly this RFC proposes a spam protection mechanism based on Proof-of-Work (PoW) where only those +messages are accepted and propagated by the honest nodes that include a valid nonce value. That nonce serves as a +proof to show that a predefined amount of computational work has been done upfront either by the publisher itself or +by some PoW service provider on its behalf. # Motivation -In order to protect the network from spam each device that wishes to get a message (e.g. a value transaction) propagated by all the nodes needs a way to proove to the network, that it has invested the necessary amount of computational work either by doing the computation by itself, or by externalizing it using some PoW service provider. +Since IOTA is a permissionless and feeless network it needs methods of protecting itself against spam from +intentionally or even unintentionally misbehaving network participants (nodes or clients) that otherwise could +disrupt the network rather cheaply. Furthermore, this mechanism is currently (at the time of this writing) used in +the IOTA mainnet, and necessary to build compatible nodes with this framework. -### Background info on PoW -Cryptographic hash functions are one-way-functions. That means in simple terms that given the output you cannot calculate the input from it. Proof-of-Work works by setting some constraint on the output like a certain number of zeros at the end or the beginning of the hash. It's completely arbitrary. It could be ten `1`s or the sequence `1234567890` because any particular sequence is equally likely. What matters is that your only chance to find an input that satisfies the constraint is guessing/brute-forcing it. You can also easily set the difficulty by making the constraint harder to satisfy (extend the sequence). So to check if PoW was correctly done all a validating node has to do is hash the given message which includes the nonce, and see if the constraint is satisfied. On the other hand the message publisher has to repeat the following cycle many times until it has found a valid nonce: -* Pick a nonce value (e.g. by simply increasing it, or choose it randomly) -* Hash the message together with selected nonce -* Compare if the hash satisfies the constraint given by the majority of the network +### Job stories: -This process is called `Mining` and in this analogy the nonce is the nugget. +When I - as a client - want to send a message, for example a value transaction, I want to be able to find and include +a valid nonce, so that my transaction is accepted and propagated by all honest nodes in the network. -TODO +When I - as a node - receive a network message from the gossip pipeline, I want to be able to check if its contained +nonce value satisfies the pre-defined network difficulty, so that I can decide whether to discard or process that +message. -Why are we doing this? What use cases does it support? What is the expected -outcome? +# Detailed design -1. Write a summary of the motivation. -2. List all the specific use cases that your proposal is trying to address. - the software, for example using the "Job story" format: +WIP (various designs are currently evaluated in small prototype implementations) -TODO -### Job stories: -When I create a gossip message (e.g. a transaction), I want to be able to find a nonce, so that hashing the message and the nonce creates a hash that satisfies the constraint set by the network. +For more information on PoW in general and the relation to the proposed `Transaction` struct please read: +* [Minimum Weight Magnitude](https://docs.iota.org/docs/dev-essentials/0.1/concepts/minimum-weight-magnitude), +* [Transaction/Bundle RFC](https://github.com/iotaledger/bee-rfcs/pull/20) -When I receive a gossip message, I want to be able to verify, that the contained nonce is valid, so that I know that the publisher of that message has at least invested some resource to find it. +# Drawbacks +A spam protection mechanism based on PoW +* consumes energy for doing throw-away calculations, +* slows down all participants even if there's no attack (always-on defense), +* is not favorable for battery-driven devices, many of which form the IoT space, +* amplifies problems that arise in networks where nodes have diverse computation capabilities like in the IoT space -`IMPORTANT NOTE` -It is important that this crate is independent from the particular hashfunction. Ideally it would not even depend on whether a trinary or a binary hashfunction is used. +# Rationale and alternatives -# Detailed design +- Why is this design the best in the space of possible designs? -`This is the bulk of the RFC. Explain the design in enough detail for somebody -familiar with the IOTA and to understand, and for somebody familiar with Rust -to implement. This should get into specifics and corner-cases, and include -examples of how the feature is used.` +The main purpose of this proposal is to support building nodes that can interoperate with the current IOTA mainnet. -For a prototype implementation please checkout [bee-pow-preview](https://github.com/Alex6323/bee-pow-preview). +- What other designs have been considered and what is the rationale for not choosing them? -# Drawbacks +No other designs have been considered. Other anti-spam mechanisms are still being researched. -Why should we *not* do this? +- What is the impact of not doing this? -# Rationale and alternatives +Not doing this means that nodes built with this framework are incompatible with the current IOTA mainnet. -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not - choosing them? -- What is the impact of not doing this? # Unresolved questions -- What parts of the design do you expect to resolve through the RFC process - before this gets merged? -- What parts of the design do you expect to resolve through the implementation - of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be - addressed in the future independently of the solution that comes out of this - RFC? +- How modular do we want this crate to be? +- Should we focus on Curl or make the hashfunction plug and play? +- Should we design this a kind of service component employing its own runtime that performs PoW jobs asynchronously? +- Should support for external PoW be part of the implementation following the proposal? \ No newline at end of file From 5598d29dd24036f3a504d1c9e3e3190b19314591 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Thu, 31 Oct 2019 18:13:51 +0100 Subject: [PATCH 6/8] Added detailed design section and code examples --- text/0000-bee-pow/0000-bee-pow.md | 271 +++++++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 3 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index d5625758..74ee1fc5 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -5,7 +5,7 @@ # Summary -IOTA is a permissionless and feeless network which unavoidably makes it vulnerable to spam attacks. To make such +IOTA is a permissionless and feeless network which unavoidably makes it vulnerable to spam and sybil attacks. To make such attacks more costly this RFC proposes a spam protection mechanism based on Proof-of-Work (PoW) where only those messages are accepted and propagated by the honest nodes that include a valid nonce value. That nonce serves as a proof to show that a predefined amount of computational work has been done upfront either by the publisher itself or @@ -29,12 +29,277 @@ message. # Detailed design -WIP (various designs are currently evaluated in small prototype implementations) +## Hasher +A hash function always operates on some kind of input data, usually but not necessarily on some kind of `u8` array - and deterministically produces another piece of data - the hash - for example a `u64` that usually takes much less space, and therefore can be used as a label for the data it was derived from. In Rust such a hash function can be generalized using the following trait: +```Rust +pub trait Hasher { + type Data; + type Hash; -For more information on PoW in general and the relation to the proposed `Transaction` struct please read: + fn hash(&mut self, data: &Self::Data) -> Self::Hash; +} +``` + +Notive that by making `Data` and `Hash` associated types this RFC doesn't make any commitment to the underlying representation which allows for a very flexible design. + +## Sampler + +The `Sampler` trait allows implementing different types that mutate the input data handed to the hasher on each try. The standard sampler used in IOTA simply starts with a zero nonce and increments it until it finds a valid one. There are, however, other samplers possible, like a random sampler. + +```Rust +pub trait Sampler { + type Data; + + fn next(&mut self, data: &mut Self::Data); +} +``` + +Like in the `Hasher` trait we now have to make `Data` an associated type as well, so it can always be compatible when used together in `PearlDiver`. + +## Tester + +The `Tester` trait allows implementing different types that place plug different validation schemes into `PearlDiver`, like the `Hashcash` or the `Hamming` validation scheme. + +```Rust +pub trait Tester { + type Hash; + + fn is_valid(&self, hash: &Self::Hash) -> bool; +} +``` + +As with `Sampler` the `Tester` trait needs an associated type so it can always be implemented in a compatible way and operate on something served by a type implementing the `Hasher` trait. + +## PearlDiver + +With the traits above a type can now be defined that is called `PearlDiver`. It encapsulates the following algorithm: + +Starting with some serialized transaction data (which also encodes a zero nonce): +1. hash the data +2. check if the hash satisifies some pre-defined and adjustable constraint + 1. hash satisfies constraint => stop the algorithm + 2. hash doesn't satisfy constraint + 1. select another nonce + 2. goto 1. + +This RFC proposes the following `PearlDiver` type: + +```Rust +pub struct PearlDiver +where + H: Hasher, + S: Sampler, + T: Tester, +{ + hasher: H, + sampler: S, + tester: T, +} + +impl PearlDiver +where + H: Hasher, + S: Sampler, + T: Tester, +{ + pub fn new(hasher: H, sampler: S, tester: T) -> Self { + Self { + hasher, + sampler, + tester, + } + } + + pub fn search(&mut self, mut data: &mut D) { + loop { + let hash = self.hasher.hash(data); + if self.tester.is_valid(&hash) { + break; + } else { + self.sampler.next(&mut data); + } + } + } +} +``` +## PoW + +Proof-of-Work essentially is "just" about finding a piece of data (the Nonce) that is - in some way - associated with the data at hand. This proposal therefore introduces a special `PoW` trait for that. + +```Rust +pub trait PoW { + type Data; + type Nonce; + + fn get_nonce(&mut self, data: Self::Data) -> PoWResult); +} +``` +whereby + +```Rust +pub type PoWError = Box; +pub type PoWResult = Result; +``` + +Once implemented on a type, doing Proof-of-Work is a simple method call, and everything is hidden behind the scenes. The type could either perform the work locally, or for nodes on restricted devices, be something that queries a subscription-based PoW service. + +## Code Example + +### Implement the `Hasher` trait for `Curl` + +`Curl` is a ternary hash function implementation used in the IOTA ecosystem. Its exact implementation is not of interest in this proposal, just how the `Hasher` trait can be implemented on it: + +```Rust +struct Curl<'a> { + num_rounds: usize, + phantom: PhantomData<&'a i8>, +} + +impl<'a> Curl<'a> { + // implementation details (inner workings of Curl) +} + +impl<'a> Hasher for Curl<'a> { + type Data = &'a mut [i8; 8019]; + type Hash = [i8; CURL_HASH_LENGTH]; + + fn hash(&self, trits: &Self::Data) -> Self::Hash { + // code goes here that determines the Curl hash from the given trits + } +} + +``` + +The `PhantomData` field on `Curl` is necessary, because the lifetime is introduced by the associated type `Data`, not by any `Curl` field. There is no overhead in using `PhantomData`. + +### Implement the `Sampler` trait for `IncrementalNonceSampler` + +The following Rust code shows an example implementation of a ternary based nonce sampler, that simply increments the tested nonce using a ternary increment operation: + +```Rust +struct IncrementalNonceSampler<'a> { + current: [i8; NONCE_TRIT_LENGTH], + step; usize, + phantom: PhantomData<&'a i8>, +} + +impl<'a> IncrementalNonceSampler<'a> { + pub fn new(step: usize) -> Self { + Self { + current_nonce: [0_i8; NONCE_TRIT_LENGTH], + step, + phantom: PhantomData, + } + } +} + +impl<'a> Sampler for IncrementalNonceSampler<'a> { + type Data = &'a mut [i8; 8019]; + + fn next(&mut self, data: &mut Self::Data) { + for _ in 0..self.step { + // this function calls into a ternary crate that provides the ability to increment trit sequences + increment(&mut self.current_nonce, NONCE_TRIT_LENGTH) + } + data[7938..8019].copy_from_slice(&self.curent_nonce[0..NONCE_TRIT_LENGTH]); + } + +} +``` + +### Implement the `Tester` trait for `Hashcash` + +`Hashcash` is a label for something that checks if a certain number of subsequent digits are equal (for example equally 0). The number of digits included is called the difficulty, or in IOTA's case the `minimum weight magnitude`. An example Rust implementation implementing the proposed `Tester` trait would look like this: + +```Rust +struct Hashcash { + pub fn new(minimum_weight_magnitude: usize) -> Self { + Self { + minimum_weight_magnitude, + } + } +} + +impl Tester for Hashcash { + type Hash = [i8; CURL_HASH_LENGTH]; + + fn is_valid(&self, hash: &Self::Hash) -> bool { + let start_index = CURL_HASH_LENGH - self.minimum_weight_magnitude; + for i in start_index..CURL_HASH_LENGTH { + if hash[i] != 0 { + return false; + } + } + true + } +} +``` + +### Implement the `PoW` trait for `CurlHashcashPoW` + +To abstract the details of the hashing function, the sampler and the tester away it makes sense to introduce a wrapper type `CurlHashcashPoW`, which operates on 8019 trits, uses Curl as hasher, and implements the `PoW` trait: + +```Rust +struct CurlHashcashPoW<'a> { + pearldiver: PearlDiver, IncrementalNonceSampler<'a>, Hashcash, &'a mut [i8; 8019], [i8; 243]>, +} + +impl<'a> CurlHashcashPoW<'a> { + pub fn new(num_rounds: usize, minimum_weight_magnitude: usize) -> Self { + + let curl = Curl::new(num_rounds); + let sampler = IncrementalNonceSampler::new(1); + let hashcash = Hashcash::new(minimum_weight_magnitude); + + Self { pearldiver: PearlDiver::new(curl, sampler, hashcash) } + } +} + +impl<'a> PoW for CurlHashcashPow<'a> { + type Input = &'a mut [i8; 8019]; + type Nonce = [i8; NONCE_TRIT_LENGTH]; + + fn get_nonce(&mut self, mut input: Self::input) -> PoWResult { + self.pearldiver.search(&mut input); + + let mut nonce = [0_i8; NONCE_TRIT_LENGTH]; + nonce[0..NONCE_TRIT_LENGTH].copy_from_slice(&input[7938..8019]); + + Ok(nonce) + } +} +``` + +### Use `CurlHashcashPoW` type to perform Proof-of-Work on a transaction and update its nonce + +Having all that in place doing Proof-of-Work for a single transaction (provided by some other crate) can be done with a few simple instructions: + +```Rust +// Create a Curl and Hashcash based PoW instance with: +// number of Curl rounds: 81 +// minimum_weight_magnitude: 9 +let mut pow = CurlHashcashPoW::new(81, 9); + +// get the current trit represantation of a transaction (the last 81 trits should be all 0) +let tx_trits = transaction.get_trits(); + +// +let nonce = match pow.get_nonce(&mut tx_trits) { + Ok(nonce) => nonce, + Err(e) => panic!("{:?}", e), +} + +transaction.set_nonce(nonce); + +``` +Note that this proposal doesn't make suggestions about how to parallelize the Proof-Of-Work algorith, how which async library to use. The implementor can have a look at `iota.rs` for an inspiration how to do this with the `crossbeam` crate. + + +For more information on PoW, and how it can be impemented in Rust the following links might be helpful: * [Minimum Weight Magnitude](https://docs.iota.org/docs/dev-essentials/0.1/concepts/minimum-weight-magnitude), * [Transaction/Bundle RFC](https://github.com/iotaledger/bee-rfcs/pull/20) +* [iota.rs](https://github.com/iotaledger/iota.rs) # Drawbacks From 555db7a862a7da8617829e0310e38cf09d26171e Mon Sep 17 00:00:00 2001 From: /alex/ Date: Thu, 21 Nov 2019 03:03:08 +0100 Subject: [PATCH 7/8] Reduced generics and parameterization to a minimum --- text/0000-bee-pow/0000-bee-pow.md | 138 +++++++++++++++--------------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index 74ee1fc5..654fc1d0 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -5,116 +5,120 @@ # Summary -IOTA is a permissionless and feeless network which unavoidably makes it vulnerable to spam and sybil attacks. To make such -attacks more costly this RFC proposes a spam protection mechanism based on Proof-of-Work (PoW) where only those -messages are accepted and propagated by the honest nodes that include a valid nonce value. That nonce serves as a -proof to show that a predefined amount of computational work has been done upfront either by the publisher itself or -by some PoW service provider on its behalf. +IOTA is a distributed ledger technology (DLT), in which the transactions that are happening on the network are disseminated employing a gossip protocol. Each node can serve as an entry point for new transactions (issued by its clients), which will then ideally be gossiped to all other nodes in the network (or shard) and become part of the ledger state. Gossipping a received transaction is a service granted to each individual node. But since there are no fees in IOTA, and no restrictions on who can join the network, this opens up opportunities for exploitation. + +To mainly address accidental spam (e.g. from a malfunctioning node), this RFC proposes a CPU-bound Proof-of-Work (PoW) mechanism based on the ternary hash function Curl-P-81 and [Hashcash](https://en.wikipedia.org/wiki/Hashcash) with a globally fixed (but adjustable) difficulty. # Motivation -Since IOTA is a permissionless and feeless network it needs methods of protecting itself against spam from -intentionally or even unintentionally misbehaving network participants (nodes or clients) that otherwise could -disrupt the network rather cheaply. Furthermore, this mechanism is currently (at the time of this writing) used in -the IOTA mainnet, and necessary to build compatible nodes with this framework. +It must be stressed that this proposal is by no means a definitive answer to an attacker that can make use of hardware accelerated PoW. However, this mechanism - which is called `PearlDiver` - is currently used in the IOTA mainnet (at the time of writing: IRI v1.8.2) to prevent simple forms of spam. For compatibility reasons it is therefore required to be implemented in the Bee node framework as well. -### Job stories: +# Detailed design -When I - as a client - want to send a message, for example a value transaction, I want to be able to find and include -a valid nonce, so that my transaction is accepted and propagated by all honest nodes in the network. +The Rust implementation of `PearlDiver` as proposed here is supposed to be as type-safe, efficient, and Rust idiomatic as possible. -When I - as a node - receive a network message from the gossip pipeline, I want to be able to check if its contained -nonce value satisfies the pre-defined network difficulty, so that I can decide whether to discard or process that -message. +## `PearlDiver` overview +For some transaction data `PearlDiver` finds a piece of data - called a `nonce` - that, when appended and hashed together with the transaction data results in a hash with a certain property. Finding that `nonce` is intended to be a CPU intensive task that requires brute-force, i.e. sampling the search space of possible nonces and rehashing many times. Validating a "powed" transaction however can happen with a single function call, and is therefore orders of magnitude faster. This allows to quickly identify invalid transactions, whereby "invalid" in terms of Hashcash based PoW means a transaction with a nonce, that results in a transaction hash without that property. -# Detailed design +## `PearlDiver` algorithm +The basic `PearlDiver` algorithm looks like this: -## Hasher -A hash function always operates on some kind of input data, usually but not necessarily on some kind of `u8` array - and deterministically produces another piece of data - the hash - for example a `u64` that usually takes much less space, and therefore can be used as a label for the data it was derived from. In Rust such a hash function can be generalized using the following trait: +Given some pre-defined hash constraint `C`, a transaction `T` with a nonce set to 0, and its serialized representation called `data`: +1. hash `data` yielding `H` +2. check if `H` satisfies `C` (e.g. at least last 14 trits are 0). There are two cases: + 1. `C` is satisfied -> stop the algorithm, + 2. otherwise -> select the next nonce from the search space, modify `data` with it, and return to 1. -```Rust -pub trait Hasher { - type Data; - type Hash; +## `PearlDiver` assumptions - fn hash(&mut self, data: &Self::Data) -> Self::Hash; -} -``` +Instead of making `PearlDiver` extremely generic from the get-go, this proposal makes the following assumptions, and leaves everything else to future RFCs or improvement proposals. -Notive that by making `Data` and `Hash` associated types this RFC doesn't make any commitment to the underlying representation which allows for a very flexible design. +`PearlDiver` +* receives transactions in serialized form using the `t1b1` ternary encoding, and has to convert that into `ptrits`/`t8b2`/ binary-coded ternary (BCT) prior to its execution, +* employs a Curl-P-81 implementation that operates on `ptrits`, +* allows to employ all available CPU cores on the system, +* supports async/non-blocking operation -## Sampler +## The `HashValidator` trait -The `Sampler` trait allows implementing different types that mutate the input data handed to the hasher on each try. The standard sampler used in IOTA simply starts with a zero nonce and increments it until it finds a valid one. There are, however, other samplers possible, like a random sampler. +In IOTA two hash validation schemes are of importance: +* *Hashcash* (the number of zero trits at the end of a transaction hash) +* *Hamming* (TODO) -```Rust -pub trait Sampler { - type Data; +To allow implementing various of those schemes the following trait is introduced: - fn next(&mut self, data: &mut Self::Data); +```Rust +trait HashValidator { + fn is_valid(&self, hash: &BCTHash) -> bool; } ``` -Like in the `Hasher` trait we now have to make `Data` an associated type as well, so it can always be compatible when used together in `PearlDiver`. +This trait requires to implement a method `is_valid` which contains the logic for the hash validation. It expects a reference to a bct-encoded hash, which is also the output of the BCT-Curl implementation. -## Tester +`PearlDiver` is bound to a type implementing that trait to determine when a search has successfully completed, that is, when a valid nonce has been found. -The `Tester` trait allows implementing different types that place plug different validation schemes into `PearlDiver`, like the `Hashcash` or the `Hamming` validation scheme. +**Code Example:** ```Rust -pub trait Tester { - type Hash; +struct Hashcash { + min_weight_magnitude: usize, +}; - fn is_valid(&self, hash: &Self::Hash) -> bool; +impl HashValidator for Hashcash { + fn is_valid(&self, hash: &BCTHash) -> bool { + // TODO + } } ``` -As with `Sampler` the `Tester` trait needs an associated type so it can always be implemented in a compatible way and operate on something served by a type implementing the `Hasher` trait. +## The `BCTNonceSampler` struct -## PearlDiver +This RFC proposes a `BCTNonceSampler` struct, that can be configured to start walking the search space at a pre-defined index, and in pre-defined steps. This is used to create disjoint subsets of the whole search space, that concurrent threads can process without doing double work. Since the underlying ... -With the traits above a type can now be defined that is called `PearlDiver`. It encapsulates the following algorithm: +```Rust -Starting with some serialized transaction data (which also encodes a zero nonce): -1. hash the data -2. check if the hash satisifies some pre-defined and adjustable constraint - 1. hash satisfies constraint => stop the algorithm - 2. hash doesn't satisfy constraint - 1. select another nonce - 2. goto 1. +struct BCTNonceSampler { + start: usize, + step: usize, +} + +impl BCTNonceSampler { + pub fn new(start: usize, step: usize) -> Self { + Self { + start, + step, + } + } +} + +``` + +## The `PearlDiver` struct This RFC proposes the following `PearlDiver` type: ```Rust -pub struct PearlDiver +struct PearlDiver where - H: Hasher, - S: Sampler, - T: Tester, + V: HashValidator, { - hasher: H, - sampler: S, - tester: T, + validator: V, + hasher: BCTCurl, + sampler: } -impl PearlDiver +impl PearlDiver where - H: Hasher, - S: Sampler, - T: Tester, + V: HashValidator, { - pub fn new(hasher: H, sampler: S, tester: T) -> Self { - Self { - hasher, - sampler, - tester, - } + pub fn new(validator: V) -> Self { + Self { validator } } - pub fn search(&mut self, mut data: &mut D) { + pub fn search(&mut self, mut data: &mut BCTTransaction) { loop { let hash = self.hasher.hash(data); - if self.tester.is_valid(&hash) { + if self.validator.is_valid(&hash) { break; } else { self.sampler.next(&mut data); From 4384bebfdee9f93a124c3a9e795c2cc8173471af Mon Sep 17 00:00:00 2001 From: /alex/ Date: Wed, 11 Dec 2019 13:25:23 +0100 Subject: [PATCH 8/8] Aligned RFC with state-of-the-art IOTA PoW impls. --- text/0000-bee-pow/0000-bee-pow.md | 336 +++++++----------------------- 1 file changed, 71 insertions(+), 265 deletions(-) diff --git a/text/0000-bee-pow/0000-bee-pow.md b/text/0000-bee-pow/0000-bee-pow.md index 654fc1d0..7566e168 100644 --- a/text/0000-bee-pow/0000-bee-pow.md +++ b/text/0000-bee-pow/0000-bee-pow.md @@ -5,313 +5,122 @@ # Summary -IOTA is a distributed ledger technology (DLT), in which the transactions that are happening on the network are disseminated employing a gossip protocol. Each node can serve as an entry point for new transactions (issued by its clients), which will then ideally be gossiped to all other nodes in the network (or shard) and become part of the ledger state. Gossipping a received transaction is a service granted to each individual node. But since there are no fees in IOTA, and no restrictions on who can join the network, this opens up opportunities for exploitation. +This RFC intends to add a Rust implementation of the local Proof-of-Work algorithm *PearlDiver* as used in many places throughout the IOTA codebase. The performance should be on par with the (non-SIMD) C version, but offer more type-safety, better usability and integration into an asynchronous context. -To mainly address accidental spam (e.g. from a malfunctioning node), this RFC proposes a CPU-bound Proof-of-Work (PoW) mechanism based on the ternary hash function Curl-P-81 and [Hashcash](https://en.wikipedia.org/wiki/Hashcash) with a globally fixed (but adjustable) difficulty. # Motivation -It must be stressed that this proposal is by no means a definitive answer to an attacker that can make use of hardware accelerated PoW. However, this mechanism - which is called `PearlDiver` - is currently used in the IOTA mainnet (at the time of writing: IRI v1.8.2) to prevent simple forms of spam. For compatibility reasons it is therefore required to be implemented in the Bee node framework as well. +At the time of this writing IOTA utilizes a form of computational Proof-of-Work (PoW) to detect a certain category of invalid messages at the networking layer. For that, a node has to calculate and check the hash of each uniquely received message for a specific property. In IOTA this usually means that a pre-defined number of trailing trits in the hash (Curl) all have to be zero for it to be considered valid in that regard. Because of the one-wayness of the underlying hashing function, finding a hash with such a property involves brute-force on the issuing side. This attaches a certain real-world cost to each network message, which in theory should discourage sending useless messages. However, the proposed implementation cannot prevent more sophisticated spam making use of hardware accelerated PoW, and its main purpose is to achieve compatibility with current IOTA networks. -# Detailed design - -The Rust implementation of `PearlDiver` as proposed here is supposed to be as type-safe, efficient, and Rust idiomatic as possible. - -## `PearlDiver` overview -For some transaction data `PearlDiver` finds a piece of data - called a `nonce` - that, when appended and hashed together with the transaction data results in a hash with a certain property. Finding that `nonce` is intended to be a CPU intensive task that requires brute-force, i.e. sampling the search space of possible nonces and rehashing many times. Validating a "powed" transaction however can happen with a single function call, and is therefore orders of magnitude faster. This allows to quickly identify invalid transactions, whereby "invalid" in terms of Hashcash based PoW means a transaction with a nonce, that results in a transaction hash without that property. - -## `PearlDiver` algorithm -The basic `PearlDiver` algorithm looks like this: - -Given some pre-defined hash constraint `C`, a transaction `T` with a nonce set to 0, and its serialized representation called `data`: -1. hash `data` yielding `H` -2. check if `H` satisfies `C` (e.g. at least last 14 trits are 0). There are two cases: - 1. `C` is satisfied -> stop the algorithm, - 2. otherwise -> select the next nonce from the search space, modify `data` with it, and return to 1. - -## `PearlDiver` assumptions - -Instead of making `PearlDiver` extremely generic from the get-go, this proposal makes the following assumptions, and leaves everything else to future RFCs or improvement proposals. - -`PearlDiver` -* receives transactions in serialized form using the `t1b1` ternary encoding, and has to convert that into `ptrits`/`t8b2`/ binary-coded ternary (BCT) prior to its execution, -* employs a Curl-P-81 implementation that operates on `ptrits`, -* allows to employ all available CPU cores on the system, -* supports async/non-blocking operation - -## The `HashValidator` trait - -In IOTA two hash validation schemes are of importance: -* *Hashcash* (the number of zero trits at the end of a transaction hash) -* *Hamming* (TODO) - -To allow implementing various of those schemes the following trait is introduced: -```Rust -trait HashValidator { - fn is_valid(&self, hash: &BCTHash) -> bool; -} -``` - -This trait requires to implement a method `is_valid` which contains the logic for the hash validation. It expects a reference to a bct-encoded hash, which is also the output of the BCT-Curl implementation. - -`PearlDiver` is bound to a type implementing that trait to determine when a search has successfully completed, that is, when a valid nonce has been found. - -**Code Example:** - -```Rust -struct Hashcash { - min_weight_magnitude: usize, -}; - -impl HashValidator for Hashcash { - fn is_valid(&self, hash: &BCTHash) -> bool { - // TODO - } -} -``` - -## The `BCTNonceSampler` struct - -This RFC proposes a `BCTNonceSampler` struct, that can be configured to start walking the search space at a pre-defined index, and in pre-defined steps. This is used to create disjoint subsets of the whole search space, that concurrent threads can process without doing double work. Since the underlying ... - -```Rust - -struct BCTNonceSampler { - start: usize, - step: usize, -} - -impl BCTNonceSampler { - pub fn new(start: usize, step: usize) -> Self { - Self { - start, - step, - } - } -} - -``` - -## The `PearlDiver` struct - -This RFC proposes the following `PearlDiver` type: - -```Rust -struct PearlDiver -where - V: HashValidator, -{ - validator: V, - hasher: BCTCurl, - sampler: -} - -impl PearlDiver -where - V: HashValidator, -{ - pub fn new(validator: V) -> Self { - Self { validator } - } - - pub fn search(&mut self, mut data: &mut BCTTransaction) { - loop { - let hash = self.hasher.hash(data); - if self.validator.is_valid(&hash) { - break; - } else { - self.sampler.next(&mut data); - } - } - } -} -``` -## PoW - -Proof-of-Work essentially is "just" about finding a piece of data (the Nonce) that is - in some way - associated with the data at hand. This proposal therefore introduces a special `PoW` trait for that. - -```Rust -pub trait PoW { - type Data; - type Nonce; - - fn get_nonce(&mut self, data: Self::Data) -> PoWResult); -} -``` -whereby - -```Rust -pub type PoWError = Box; -pub type PoWResult = Result; -``` - -Once implemented on a type, doing Proof-of-Work is a simple method call, and everything is hidden behind the scenes. The type could either perform the work locally, or for nodes on restricted devices, be something that queries a subscription-based PoW service. - -## Code Example +# Detailed design -### Implement the `Hasher` trait for `Curl` +In the current transaction model the following constants are relevant for a `PearlDiver` implementation. -`Curl` is a ternary hash function implementation used in the IOTA ecosystem. Its exact implementation is not of interest in this proposal, just how the `Hasher` trait can be implemented on it: +## Proposed constants ```Rust -struct Curl<'a> { - num_rounds: usize, - phantom: PhantomData<&'a i8>, -} - -impl<'a> Curl<'a> { - // implementation details (inner workings of Curl) -} +/// The size of a transaction in trits (at the time of this writing). +const TRANSACTION_LENGTH: usize = 8019; -impl<'a> Hasher for Curl<'a> { - type Data = &'a mut [i8; 8019]; - type Hash = [i8; CURL_HASH_LENGTH]; - - fn hash(&self, trits: &Self::Data) -> Self::Hash { - // code goes here that determines the Curl hash from the given trits - } -} +/// The size of a nonce in trits. +const NONCE_LENGTH: usize = 81; +/// The full state size of the Curl hash function. +const CURL_STATE_LENGTH: usize = 729; ``` -The `PhantomData` field on `Curl` is necessary, because the lifetime is introduced by the associated type `Data`, not by any `Curl` field. There is no overhead in using `PhantomData`. - -### Implement the `Sampler` trait for `IncrementalNonceSampler` +## Proposed new-types -The following Rust code shows an example implementation of a ternary based nonce sampler, that simply increments the tested nonce using a ternary increment operation: +To increase type-safety and readibility this RFC proposes the following new-types (wrapper types): ```Rust -struct IncrementalNonceSampler<'a> { - current: [i8; NONCE_TRIT_LENGTH], - step; usize, - phantom: PhantomData<&'a i8>, -} - -impl<'a> IncrementalNonceSampler<'a> { - pub fn new(step: usize) -> Self { - Self { - current_nonce: [0_i8; NONCE_TRIT_LENGTH], - step, - phantom: PhantomData, - } - } -} +/// A new-type to represent the input for PearlDiver, which are balanced trits {-1, 0, 1} (T1B1 encoding). +struct Input([i8; TRANSACTION_LENGTH]); -impl<'a> Sampler for IncrementalNonceSampler<'a> { - type Data = &'a mut [i8; 8019]; +/// A new-type to represent the output of PearlDiver in balanced trits {-1, 0, 1} (T1B1 encoding). +struct Nonce([i8; NONCE_LENGTH]); - fn next(&mut self, data: &mut Self::Data) { - for _ in 0..self.step { - // this function calls into a ternary crate that provides the ability to increment trit sequences - increment(&mut self.current_nonce, NONCE_TRIT_LENGTH) - } - data[7938..8019].copy_from_slice(&self.curent_nonce[0..NONCE_TRIT_LENGTH]); - } +/// A new-type to represent a valid number of logical cores to use for `PearlDiver`. Its purpose is to guarantee only valid `usize`s. +#[derive(Clone)] +struct CoreCount(usize); -} +/// A new-type to represent a valid minimum weight magnitude value, that is, the PoW difficulty. Its purpose is to guarantee only valid `usize`s. +#[derive(Clone)] +struct Difficulty(usize); ``` -### Implement the `Tester` trait for `Hashcash` - -`Hashcash` is a label for something that checks if a certain number of subsequent digits are equal (for example equally 0). The number of digits included is called the difficulty, or in IOTA's case the `minimum weight magnitude`. An example Rust implementation implementing the proposed `Tester` trait would look like this: +## Proposed models ```Rust -struct Hashcash { - pub fn new(minimum_weight_magnitude: usize) -> Self { - Self { - minimum_weight_magnitude, - } - } -} - -impl Tester for Hashcash { - type Hash = [i8; CURL_HASH_LENGTH]; - - fn is_valid(&self, hash: &Self::Hash) -> bool { - let start_index = CURL_HASH_LENGH - self.minimum_weight_magnitude; - for i in start_index..CURL_HASH_LENGTH { - if hash[i] != 0 { - return false; - } - } - true - } +/// The different states `PearlDiver` can be in. +#[derive(Debug, Eq, PartialEq)] +enum PearlDiverState { + /// The initial state after instantiation. + Initialized, + /// The state while `PearlDiver` is searching for a nonce. + Searching, + /// The state when `PearlDiver` is aborted. + Cancelled, + /// The state when `PearlDiver` completed either with a nonce as a result, or no result at all. + Completed, +} + +/// The `PearlDiver` abstraction itself. +#[derive(Clone)] +struct PearlDiver { + // The number of cores to use. + num_cores: CoreCount, + // The number of trailing zero trits to search for. + difficulty: Difficulty, + // The shared state of `PearlDiver`. But reads will happen much more often then writes. + state: Arc>, } ``` -### Implement the `PoW` trait for `CurlHashcashPoW` - -To abstract the details of the hashing function, the sampler and the tester away it makes sense to introduce a wrapper type `CurlHashcashPoW`, which operates on 8019 trits, uses Curl as hasher, and implements the `PoW` trait: +Since doing something like Proof-of-Work is a time-consuming operation (doens't matter if done locally or remotely) it matches the very definition of a `Future` (a computation that may yield a result at some point in the future). It allows us to write high-performant non-blocking/asynchronous code. For that this RFC proposes to model the `PearlDiver` search as a `std::future::Future`. ```Rust -struct CurlHashcashPoW<'a> { - pearldiver: PearlDiver, IncrementalNonceSampler<'a>, Hashcash, &'a mut [i8; 8019], [i8; 243]>, -} - -impl<'a> CurlHashcashPoW<'a> { - pub fn new(num_rounds: usize, minimum_weight_magnitude: usize) -> Self { - - let curl = Curl::new(num_rounds); - let sampler = IncrementalNonceSampler::new(1); - let hashcash = Hashcash::new(minimum_weight_magnitude); - - Self { pearldiver: PearlDiver::new(curl, sampler, hashcash) } - } +/// A `std::future::Future` that completes once `PearlDiver` finds Some(nonce), None or gets cancelled. +struct PearlDiverSearch { + // A cloned instance of `PearlDiver` + pearldiver: PearlDiver, } -impl<'a> PoW for CurlHashcashPow<'a> { - type Input = &'a mut [i8; 8019]; - type Nonce = [i8; NONCE_TRIT_LENGTH]; - - fn get_nonce(&mut self, mut input: Self::input) -> PoWResult { - self.pearldiver.search(&mut input); - - let mut nonce = [0_i8; NONCE_TRIT_LENGTH]; - nonce[0..NONCE_TRIT_LENGTH].copy_from_slice(&input[7938..8019]); +impl std::future::Future for PearlDiverSearch { + type Ouput = Option; - Ok(nonce) + fn poll(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context) -> Poll { + // this future continuesly polls `PearlDiver`s state, which can be updated by any of the threads + // it completes, once a poll yields either: + // * PearlDiverState::Cancelled + // * PearlDiverState::Completed(None) + // * PearlDiverState::Completed(Some(nonce)) } } ``` -### Use `CurlHashcashPoW` type to perform Proof-of-Work on a transaction and update its nonce - -Having all that in place doing Proof-of-Work for a single transaction (provided by some other crate) can be done with a few simple instructions: +## Proposed `PearlDiver` API: ```Rust -// Create a Curl and Hashcash based PoW instance with: -// number of Curl rounds: 81 -// minimum_weight_magnitude: 9 -let mut pow = CurlHashcashPoW::new(81, 9); - -// get the current trit represantation of a transaction (the last 81 trits should be all 0) -let tx_trits = transaction.get_trits(); - -// -let nonce = match pow.get_nonce(&mut tx_trits) { - Ok(nonce) => nonce, - Err(e) => panic!("{:?}", e), +impl PearlDiver { + /// Creates a new `PearlDiver`, and tells it to use a certain number of cores, and a pre-defined difficulty. + pub fn new(num_cores: CoreCount, difficulty: Difficulty) -> Self {} + /// Initiates the search for a nonce by immediately return a future that can be spawned onto some runtime. + pub fn search(&mut self, input: &Input) -> PearlDiverSearch {} + /// Cancels `PearlDiver` which causes the future to complete immediatedly without a result. + pub fn cancel(&mut self) {} } -transaction.set_nonce(nonce); - ``` -Note that this proposal doesn't make suggestions about how to parallelize the Proof-Of-Work algorith, how which async library to use. The implementor can have a look at `iota.rs` for an inspiration how to do this with the `crossbeam` crate. +A prototype implementation can be found here: -For more information on PoW, and how it can be impemented in Rust the following links might be helpful: -* [Minimum Weight Magnitude](https://docs.iota.org/docs/dev-essentials/0.1/concepts/minimum-weight-magnitude), -* [Transaction/Bundle RFC](https://github.com/iotaledger/bee-rfcs/pull/20) -* [iota.rs](https://github.com/iotaledger/iota.rs) +[pow-preview](https://github.com/Alex6323/pow-preview.git) # Drawbacks -A spam protection mechanism based on PoW -* consumes energy for doing throw-away calculations, -* slows down all participants even if there's no attack (always-on defense), -* is not favorable for battery-driven devices, many of which form the IoT space, -* amplifies problems that arise in networks where nodes have diverse computation capabilities like in the IoT space +* There are only drawbacks around Proof-of-Work in general, as it can be quite a burden for weakish IoT devices. Solutions to combat that disadvantage are being researched. The proposed design is rather minimalistic, and already implemented similarly in `iotaledger/iota.rs`. # Rationale and alternatives @@ -330,7 +139,4 @@ Not doing this means that nodes built with this framework are incompatible with # Unresolved questions -- How modular do we want this crate to be? -- Should we focus on Curl or make the hashfunction plug and play? -- Should we design this a kind of service component employing its own runtime that performs PoW jobs asynchronously? -- Should support for external PoW be part of the implementation following the proposal? \ No newline at end of file +- Should this RFC be restricted to local PoW? \ No newline at end of file