-
Notifications
You must be signed in to change notification settings - Fork 46
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
Per message type fee #376
Per message type fee #376
Conversation
a268f01
to
43b410e
Compare
- new function `NewCoinp` to create and return a pointer to a coin - `Coin.Add` method allows to add zero value coin without a ticker - update coin tests
Finish the implementation and add tests. resolve #232
This comment has been minimized.
This comment has been minimized.
@@ -128,10 +132,20 @@ func mul64(a, b int64) (int64, error) { | |||
// currencies, or if the combination would cause | |||
// an overflow | |||
func (c Coin) Add(o Coin) (Coin, error) { | |||
// If any of the coins represents no value and does not have a ticker |
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.
Q: a coin without a ticker is an invalid coin. even if it is zero, IMHO. Is this code due to #369 not done yet?
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.
That is a good point.
This code is because of https://github.com/iov-one/weave/pull/376/files#diff-e1c2020236c87568b4fce1d6a33e620cR39
It is often in weave codebase that instead of a pointer a zero value is returned and later used, for example weave.CheckResult
or mentioned coin.Coin
. If they are returned together with an error
value, they are not a pointer but an empty structure.
The above cause sometimes weird situations where a value is zero (not nil
). Because it is not nil
, all methods should apply (it is a value right?). And then you are getting into a complicated question of "does this make sense?" 😁
In this case I think it does make sense. My logic was
0 + 1 BTC = 1 BTC // zero value
0 BTC + 1 BTC = 1 BTC
0 BTC + 0 ETH = error
1 BTC + 0 ETH = error
0 BTC + 1 ETH = error
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 agree with the zero value, uninitialized struct.
Since we don't use pointers everywhere, and we mean 'noop' / 'do nothing' when returning coin.Coin{}, we can make the functions treat it special
return Coin{ | ||
Whole: whole, | ||
Fractional: fractional, | ||
Ticker: ticker, | ||
} | ||
} | ||
|
||
// NewCoinp returns a pointer to a new coin. | ||
func NewCoinp(whole, fractional int64, ticker string) *Coin { |
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.
pp: I don't see big value in adding another constructor with pointer result for the tests. I would suggest you make coin
a non null type in the protobuf via annotations so that you don't have to deal with this case.
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.
There are multiple places where coinp
is declared. This is the fourth time I was declaring it so I thought it deserves its own function.
Using non null coin reference is problematic because I want to compare with a coin pointer. This is the case in many places where a result is a coin pointer and that is what you want to check becuase a result can be both a zero value coin instance and nil
.
Does this make sense?
I have pushed a change where I replace pointer to a coin with embedded value 215557c
I still think that coin.NewCoinp
is useful in other cases and could be used instead of ad-hoc creation of coinp
function. What do you think?
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 have seen (and used) this work-around in lots of test code. Fair enough to bring it here as a helper.
x/msgfee/codec.proto
Outdated
// the message to be processed. | ||
message MsgFee { | ||
string msg_path = 1; | ||
coin.Coin fee = 2; |
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.
Q: Would it make sense to have the not nil annotation for fee?
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.
pp: fee
is a bit overloaded already. This is the product fee to be more precise
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.
Yes, it does make sense to change it to a non-pointer type. 🏃♂️
Now that you pointed it out I agree. I think ProductFee
is not describing it well. The functionality is to charge for a transaction processing, there is no product 🤔
How about simple Value
? MsgFee.Value
of type coin.Coin
.
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 like to think of our features as products especially if they come with a price
. I can not think of a better name now. Sorry. If you don't like price then please use fee
. It is better than value
as it indicates somebody has to pay for this, too. But don't feel blocked by 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.
I will stick to the Fee
then.
I can be very wrong because I am not a native speaker. price
sounds to me like I am paying coins for a product. fee
sounds like I am paying coins in addition to the price
- an extra amount required as an addition to the transaction.
In the real world when I buy a house the price is 1mln EUR plus extra expences like sales person, writing to the mortgage register, tax. The 1mln is the price
and all others are fee
. 🤔
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.
Great job.
The logic looks solid, and I think it is all correct.
But I added some small code clean-up and a few more test cases to add.
Ping me when done and I can approve it.
return Coin{ | ||
Whole: whole, | ||
Fractional: fractional, | ||
Ticker: ticker, | ||
} | ||
} | ||
|
||
// NewCoinp returns a pointer to a new coin. | ||
func NewCoinp(whole, fractional int64, ticker string) *Coin { |
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 have seen (and used) this work-around in lots of test code. Fair enough to bring it here as a helper.
@@ -128,10 +132,20 @@ func mul64(a, b int64) (int64, error) { | |||
// currencies, or if the combination would cause | |||
// an overflow | |||
func (c Coin) Add(o Coin) (Coin, error) { | |||
// If any of the coins represents no value and does not have a ticker |
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 agree with the zero value, uninitialized struct.
Since we don't use pointers everywhere, and we mean 'noop' / 'do nothing' when returning coin.Coin{}, we can make the functions treat it special
MsgPath string `json:"msg_path"` | ||
Fee coin.Coin `json:"fee"` | ||
} | ||
var fees []*msgfee |
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.
curiosity question:
why do we need []*msgFee
rather than []msgFee
?
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.
That is a great question!
This is because when iterating over a slice of non-pointers "current" value is always overwritten in each step. This means that if you reference it you will end up with the same value everywhere.
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.
Nice tip.
It took me a bit to understand where the big came from even after seeing it run.
|
||
db := store.MemStore() | ||
var ini Initializer | ||
if err := ini.FromGenesis(opts, db); err != nil { |
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.
We need to standardize on a test library....
I was using testify/assert, and I think Alex as well.
You use stdlib test, which is quite similar, but I feel requires more typing
GoConvey is quite a break using a different paradigm than table-driven tests.
Note below that you use %s
for the error, not %+v
, which would show the stacktrace.
And asset.NoError(err)
(here require.NoError(err)
would automatically give you all this info).
But yes, this has nothing to do with the PR and is a long outstanding issue
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.
There is already an issue that is waiting for a hero to pick up the "fight" #249 ⚔️ 💪
(best to read it while listen to https://www.youtube.com/watch?v=OBwS66EBUcY&t=121)
"github.com/iov-one/weave/store" | ||
) | ||
|
||
func TestGenesis(t *testing.T) { |
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.
Good happy-path test.
Adding a test with a good error value for an illegal fee would be good... we want to ensure invalid configs die early (on blockchain startup), not on first use. The code does this properly, but I like to see a test... which ensures later changes don't remove the validation accidentally
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.
This is tested already in MsgFee.Validate
but ok.
Added in c4ee0bf
} | ||
} | ||
|
||
var helpers x.TestHelpers |
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 you want to extend x.TestHelpers with these functionalities?
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.
"github.com/iov-one/weave/x" | ||
) | ||
|
||
func TestFeeDecorator(t *testing.T) { |
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.
Test looks fine, but what about a table-test here to cover a few cases...
not just valid fee present, but also:
- no fee present (with and without RequiredFee from the handler)
- fee present (with no RequiredFee from handler, with RequiredFee of same token, with RequiredFee of different ticker)
I think that is 5 cases, and you cover 1 here. Some of there are nice edge-cases to ensure working properly (adding with 0 or other currency)
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.
Adressed in a274bf5
I have copied handlerMock
because I needed it a274bf5#diff-19ce2e115f45b1e4699811f8c435dbf1R131 I will update this code (remove the copy) once we will complete #372
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.
Nice improvement. I like.
Just one more test if you can fit it in...
a274bf5#r32617130
x/msgfee/decorator_test.go
Outdated
@@ -29,6 +29,18 @@ func TestFeeDecorator(t *testing.T) { | |||
WantCheckFee: coin.NewCoin(0, 1234, "DOGE"), | |||
WantDeliverFee: coin.NewCoin(0, 1234, "DOGE"), | |||
}, | |||
"message fee with addded to existing value with the same currency": { |
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.
🎉
#232
Please notice that in ac5d8a4 I have introduced changes to the
coin
package.