Skip to content

Derivation path improvements, closes #473#498

Merged
apoelstra merged 2 commits intorust-bitcoin:masterfrom
BP-WG:pending/derivation
Dec 28, 2020
Merged

Derivation path improvements, closes #473#498
apoelstra merged 2 commits intorust-bitcoin:masterfrom
BP-WG:pending/derivation

Conversation

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky commented Oct 11, 2020

Part of epic #473

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this is basically just TryInto<DerivationPath, Error=Error>? Wish we could just use it directly (1.34.0), but if we can't maybe it would be better to just name it TryIntoDerivationPath to make the future transition easier? There might be some little kinks wrt such a transition, but since rust-bitcoin can still do breaking changes on ongoing basis, there's no point in worrying too much about it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if we were >=1.34, it would be TryInto. Regarding current naming, many Rust Into/FromSomething have the name without Try even if they fail (FromStr for instance) and I do not know any counter-examples of traits named with Try... other than TryInto/From themselves. Also this will change method naming, which will hardly derive from the naming used across this crate (where from_... usually failiable)

Copy link
Copy Markdown
Collaborator

@stevenroose stevenroose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see the merit of this trait over just having a to_derivation_path method on whatever type you'd like to implement this on or having the trait in your own project. If it was a std TryInto or so, it would allow plugging into existing places potentially, but like this it seems quite pointless.

Plus there is the extra generic impls that I suspect might cause problems for implementors of the trait.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this one of those constructions where whenever you implement IntoDerivationPath for a type, cargo will complain "can't allow this impl because rust-bitcoin can implement From<T> for DerivationPath and then this implementation is conflicting"? I bumped into that before.

@dr-orlovsky did you try this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a good solution for that which I use widely: trait implementation strategies and holder. Proposed by @Kixunil.

A sample can be found here:
https://github.com/LNP-BP/rust-lnpbp/blob/master/src/paradigms/strict_encoding.rs#L298-L445

Copy link
Copy Markdown
Collaborator Author

@dr-orlovsky dr-orlovsky Oct 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, actually, with this downstreams would not need to impl IntoDerivationPath at all, just do a Into<DerivationPath> impl and everything else will work. This is another reason of having this as a trait

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have a link to this trait thing?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But did you try to implement IntoDerivationPath on a foreign type already?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have a link to this trait thing?

Sorry, which one? Strategy? I gave it above

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how the linked code addresses Steven's concern about Rust's coherence rules

Copy link
Copy Markdown
Collaborator Author

@dr-orlovsky dr-orlovsky Oct 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three problems we are mixing here.

  1. Implementing foreign traits for foreign types. There is nothing specific for this problem with this PR, it's a common rust issue and the only way of addressing it is to do either wrapped data type or do a new marker trait with generic dependency on the foreign one (like pub trait NewOne where Self: ForeignOne { /* repeat all methods from ForeignOne */ }) and implement it on foreign data types. I do not know why and how we started discussion this problem, b/c again there is nothing in this commit which makes this problem anyhow harder to solve.

  2. Implementing trait for a generic type ("blanket implementation") more than once (applies both for local and foreign traits) - or implement foreign trait for a concrete type where there is some blanket implementation in the upstream. The solution is to use special pattern by @Kixunil. I use it widely and have a special helper type here: https://github.com/LNP-BP/rust-amplify/blob/master/src/strategy.rs#L26-L45

    With that helper type you can write the following code, which will provide you with efficiently multiple blanket implementations of some trait SampleTrait:

    // Define strategies, one per specific implementation that you need, 
    // either blanket or concrete
    pub struct StrategyA;
    pub struct StrategyB;
    pub struct StrategyC;
    
    // Define a single marker type
    pub trait Strategy {
        type Strategy;
    }
    
    // Do a single blanket implementation using Holder and Strategy marker trait
    impl<T> SampleTrait for T
    where
        T: Strategy + Clone,
        amplify::Holder<T, <T as Strategy>::Strategy>: SampleTrait,
    {
         // Do this for each of sample trait methods:
         fn sample_trait_method(&self) {
             amplify::Holder::new(self.clone()).sample_trait_method()
         }
    }
    
    // Do this type of implementation for each of the strategies
    impl<T> SampleTrait for amplify::Holder<T, StrategyA>
    where
        T: Strategy
    {
        fn sample_trait_method(&self) {
            /* ... write your implementation-specific code here */
        }
    }
    
    // Finally, apply specific implementation strategy to a concrete type
    // (or do it in a blanket generic way) as a marker:
    impl Strategy for ConcreteTypeA {
        tyoe Strategy = StrategyA;
    }
  3. Implementing type IntoDerivationPath from this PR on some concrete type in downstream dependency. This should not be done, instead one should implement Into<DerivationPath> and will get into_derivation_path() method for free, thanks to a blanket implementation from this PR, and, if for any wired (and unrecommended) reason he'd like to do a direct implementation of IntoDerivationPath for some concrete type or another blanket, he should use solution from pt.2.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, sounds like I should move my ass and finally write that article about the pattern which I planned for some time. :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main problem with downstream only being able to implement Into<DerivationPath> is that it doesn't allow failure. Idk if that's a blocker though. Imo this trait is a fixme anyway and should be deprecated as soon as MSRV >= 1.34.0, at which point this wouldn't be a problem anymore.

@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

dr-orlovsky commented Oct 14, 2020

@stevenroose without that trait you can't do stuff like fn some(impl IntoDerivationPath) which will be accepting different types of parameters, which is the whole reason behind this PR

apoelstra
apoelstra previously approved these changes Oct 14, 2020
Copy link
Copy Markdown
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack c36e471277692173ad03bfb65fcdd4106e433bf6

sgeisler
sgeisler previously approved these changes Dec 20, 2020
Copy link
Copy Markdown
Contributor

@sgeisler sgeisler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a useful and good enough stop-gap solution until we can bump our MSRV. It needs a rebase though, I'll re-ack then.

EDIT: ping @dr-orlovsky

Adding IntoDerivationPath trait
DerivationPath is_master function
DerivationPath constructor for empty path + Default impl
@dr-orlovsky dr-orlovsky dismissed stale reviews from sgeisler and apoelstra via 44ffdda December 20, 2020 22:39
@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

Rebased and ready

@dr-orlovsky dr-orlovsky mentioned this pull request Dec 20, 2020
@dr-orlovsky dr-orlovsky changed the title Derivation path improvements Derivation path improvements, closes #473 Dec 22, 2020
Copy link
Copy Markdown
Collaborator

@stevenroose stevenroose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, though probably IntoDerivationPath should probably have been named TryIntoDerivationPath since it returns a Result<DerivationPath, Error>.

DerivationPath(vec![])
}

/// Returns whether derivation path represents master key (i.e. it's length
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's should be its

Copy link
Copy Markdown
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack 44ffdda

@apoelstra apoelstra merged commit 1cc466f into rust-bitcoin:master Dec 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants