New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
transactions! macro #457
transactions! macro #457
Conversation
exonum/src/blockchain/service.rs
Outdated
/// transactions! { | ||
/// const SERVICE_ID = SERVICE_ID; | ||
/// | ||
/// MyTransactions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move const SERVICE_ID
to the MyTransaction
block? In my opinion it will be more consistent.
a11308f
to
7c60549
Compare
struct Tx { | ||
const TYPE = 1; | ||
const ID = 0; | ||
transactions! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what about more "rusty" syntax? I think that this is quite possible to do.
transactions! {
enum Transactions {
const SERVICE_ID: u16 = 0;
TxFirst {
from: &PublicKey,
data: &[u8]
},
TxSecond {
from: &PublicKey,
time: SystemTime,
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was one option I was considering, but I decided against it in the end:
The really important fact is that each of the transactions is a type on its own, it is really a standalone struct.
The fact that there's a wrapping enum is not really important: you never work with it as with an enum, you only ever use it in the boiler plate implementation of Service::tx_from_raw
and when parsing transaction from JSON, but both of this places should be handled automatically once we parametrize Service over TransactionSet
(as in #424).
On the other hand, it's quite nice that you impl Transaction
on a case by case basis.
d0c1e37
to
478b1a9
Compare
Assuming // within a separate `transactions` module
message! {
struct CreateWallet {
from: &PublicKey,
name: &str,
}
}
message! {
struct Transfer {
from: &PublicKey,
to: &PublicKey,
amount: u64,
seed: u64,
}
}
impl Transaction for CreateWallet { /* .. */ }
impl Transaction for Transfer { /* .. */ }
// note the absence of service id; same as with `message_id`,
// it's not the job of the transaction union to define it.
transaction_union! {
enum Any {
CreateWallet, // or, say, `CreateWallet(_)` for increased Rust-ness
Transfer,
}
} This assumes that I wonder if something like this is realistically possible, though. For one, it would require rethinking transaction design completely; let tx = Any::Transfer(Transfer::new(..)).with_service_id(..); |
@slowli I'd say that yes, transaction macro will stay in some form. In your example Ideally, each of individual transactions is just a One possible problem here is that signing a transaction would necessary need to violate layers :( When implementing a service, we don't care about message and service ids at all, but we must sign payload together with these ids. So, I think a reasonable plan is to merge this PR in some form and than make improvements in two directions:
|
I strongly ask for testing this PR with one of the private examples to make sure everything works as desired. Contact me if you need some help with it. |
Indeed, the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of comments as of b6e9fdc. As per some previous discussions: Is there a particular reason not to make this change backward-compatible and move all new functionality into a separate module (e.g., blockchain::ext
)? I feel that would be appropriate given that transactions!
macro is expected to be quite volatile in the nearest future.
exonum/src/blockchain/service.rs
Outdated
/// `TransactionSet` trait describes a type which is an `enum` of several transactions. | ||
/// The implementation of this trait is generated automatically by the `transactions!` | ||
/// macro. | ||
pub trait TransactionSet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit nitpick-y, but: why is the trait called *Set
? I'd say that it describes a tagged union (with variants tagged by message_id
), or enum
in Rust terms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not too thrilled by Set
myself, but I don't know of a better name :(
TransactionSet can represent either of a set of transactions, though, this is a type-level, and not value-level set.
exonum/src/blockchain/service.rs
Outdated
/// The implementation of this trait is generated automatically by the `transactions!` | ||
/// macro. | ||
pub trait TransactionSet | ||
: Into<Box<Transaction>> + DeserializeOwned + Serialize + Clone { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is Into<Box<Transaction>>
really needed? Seems like the code compiles fine without it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is used here https://github.com/exonum/exonum/pull/457/files#diff-29918e4d0be4fa7170f31c0e3e9152f8R822 and is tested by doctest for Service
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the supertrait is removed (but not impl Into<Box<Transaction>>
for types generated by transactions!
; I'm not disputing that it's needed), all lib tests and the Service
doc test still work fine (at least, for me locally). So, the question is: is the supertrait (not implementation!) needed, and what does it convey logically? More generally, I've got an impression (maybe, a mistaken one - would be glad to be corrected!) that it's considered a best practice in Rust not to use supertraits unless completely necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, indeed 🤔
The idea is that one can write a generic tx_from_raw<S: TransactionSet>
and a generic POST /transactions
methods using only : TransactionSet
bound, and not TransactionSet + Into
. This is not done in this PR to reduce its scope, but the approach should be similar to #424.
Given that we generate TransactionSet
s via macros, and we always generate the Into
impl, I don't think that the bound is particularly problematic.
However I'll try to take a holistic look at the trait hierarchy for Transaction
, Message
and StorageValue
and see if something could be drastically simplified there!
exonum/src/blockchain/service.rs
Outdated
} | ||
|
||
impl<'de> $crate::encoding::serialize::reexport::Deserialize<'de> for $transaction_set { | ||
#[allow(unused_mut)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this attribute is obsolete; there is no mut
s in the method code.
@@ -12,89 +12,75 @@ | |||
// See the License for the specific language governing permissions and | |||
// limitations under the License. | |||
|
|||
/// `message!` specifies a datatype for digitally signed messages that can be sent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that it would be relatively easy to leave message!
as is? If so, I'd recommend to do that in the light of the instability of transactions!
macro that we've discussed previously.
@@ -257,6 +262,19 @@ impl MessageWriter { | |||
} | |||
} | |||
|
|||
// This is a trait that is required for technical reasons: | |||
// you can't make associated constants object-safe. This |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly enough, if I understand correctly, the problem is with Transaction
rather than Message
. Arguably, Transaction
(at least in the form implemented by services) should not inherit from Message
at all; the first represents the wire format, the second - business logic.
Meta comment: I think that we should either not break any user code at all, or we should proactively improve APIs, without leaving fallbacks. Any middle ground strategy seems to be strictly worse than either of the extremes :) I think that at this stage we are at the "improving API" stage. With this in mind, I think that we should either decide that this is a better approach and use it instead the one with It seems to me that |
I agree, but in this case this PR does not go far enough to fall into the "proactively improving API" category, imo. It leaves |
I'd say it does improve the API measurably, because it reduces the amount of code user has to write (https://github.com/exonum/cryptocurrency-advanced/pull/99/files) and makes the typical bug "forget about a variant in tx_from_raw" harder to make. Totally agree that transactions/services need improvement as a whole, but I see |
OK, I got your point and mostly agree with it. Although I'd still leave One more thing to understand your plans better: after a change to transaction serialization, it seems like the form of the |
Status: on hold, because the direction for services is unclear. |
`TransactionSet` trait and `transactions!` macro allow to define a family of transactions for a service together, avoiding boilerplate like assigning message id or deserialization.
This PR changes the way services are defined.
This is the groundwork to implement #424 properly.
The main idea is that instead of
message!
macro, authors of servers usetransactions!
macro, which