-
Notifications
You must be signed in to change notification settings - Fork 170
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
Add initial implementation of tx formatter #96
Conversation
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.
Fantastic! Encoding/decoding is always tricky to get right. Apart from the below comments about implementation details, one general comment is that could we consider merging this package to types/
or btccheckpoint/types/
, where most BTC/checkpoint stuff is located at?
"errors" | ||
) | ||
|
||
type BabylonTag string |
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.
Could we define BabylonTag
as []byte
directly then give TestTag
and MainTag
the corresponding values? so that we don't need to mess up with the string-byte conversions.
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.
So the reasons those are strings are:
- In golang slices and arrays are not constants, but string are. I would prefer for this tag to be constants, so no one can change their values. There are probably some ways around it but it would add more boiler plate code.
- Those tag values are under this package control so we can make sure they are more valid utf8 string, so the byte conversions will not be messed up.
- Strings are more readable than bytes.
wdyt ?
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.
Agree, especially the part that we want the tag to be constant
package btctxformatter | ||
|
||
import ( | ||
"crypto/sha256" |
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.
Would it be better to use https://github.com/tendermint/tendermint/blob/main/crypto/tmhash/hash.go so that we base most of the crypto implementations on Tendermint/Cosmos?
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.
My goal here was to make this as dependency free as possible (all used deps are standard go library) so it could be easier to move it separatly publishible package or go module, so one can bring only this dependency to some submitter/reporter implementation without bringing whole cosmos sdk along side.
btctxformatter/formatter.go
Outdated
return bytes | ||
} | ||
|
||
func encodeFirstTx( |
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.
func encodeFirstTx( | |
func encodeFirst( |
This function encodes an OP_RETURN entry rather than the entire tx, so probably we don't need to mention Tx. Or if you prefer long function name we may consider encodeFirstOpReturnEntry
epoch uint64, | ||
lastCommitHash []byte, | ||
bitMap []byte, | ||
submitterAddress []byte, |
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.
submitterAddress []byte, | |
submitterAddress sdk.AccAddress, |
since we have the concrete type at the first hand when using this function.
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.
ditto, I would rather avoid on depending on sdk types here.
return serializedBytes | ||
} | ||
|
||
func getCheckSum(firstTxBytes []byte) []byte { |
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.
func getCheckSum(firstTxBytes []byte) []byte { | |
func getCheckSum(firstBytes []byte) []byte { |
ditto
btctxformatter/formatter.go
Outdated
data []byte, | ||
) ([]byte, error) { | ||
|
||
if partNumber > 1 { |
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 remember x/btccheckpoint
defined expectedProofs = 2
. Perhaps moving this constant here and using it for this check?
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 idea, ultimatly btccheckpoint will dependt on formater for getting checkpoint data without headers and validating sha.
btctxformatter/formatter.go
Outdated
|
||
// At this point this is probable babylon data, strip the header and return data | ||
// to the caller | ||
dataWithoutHeader := data[4:] |
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.
Why are we stripping the header here? I thought the vigilant reporter will forward the header bytes as well.
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.
reporter will forward whole transactions (not only op return data) as this neccessary to properly validate proofs of inclusion.
Header i.e tag + version + half number are stripped here as:
- we are calculating sha256 only over application level data and not headers, so for formatter user to properly validate sha256 headers need to be stripped.
- this way headers become internal library, so if we add there another byte or bytes later, it won't leak to other parts of codebase
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 was under the impression that the header is also a part of the input for the checksum. In fact I think it should be. This rules out the scenarios that a malicious guy submits/reports two txs that have different headers while their bodies constitute a valid checkpoint.
For example, checksum of the IPv4 protocol takes the entire header as input, and checksum of Cosmos address also takes the customised prefix as input, where the checksum is supported via bech32 format.
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 rules out the scenarios that a malicious guy submits/reports two txs that have different headers while their bodies constitute a valid checkpoint
Currently the only way this could happen is if someone would attach header of second part to to valid data of first part. Not only such thing would cost attacker btc, but it would also fail validation as first and second part have different lengths, so we would be expecting something that should have 62bytes but it would have 77bytes.
Am I missing something ?
I agree that network protocols are checksumming headers, but there the purpose of checksum is a bit different, they want to check if some bit did not become randomly corrupted during transfer. Here we do not worry about that, we just want to ease up job of reporter to match 2 halfs of the checkpoint.
Nothing here, for exemple, protects against some malicious guy sending bogus second part which will match first part, this will make reporter to submit invalid checkpoints to babylon (and therefore loss him many) but it also cost attacker to submit those bogus parts on btc, so we assume we are fine.
return bytes | ||
} | ||
|
||
func TestEncodeMainCheckpointData(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.
Fuzzing tests would be quite useful here, where we can automate the randNBytes thing above
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.
Totally agree, I intend to add fuzzing in subsequent pr's, it this pr I wanted to have only initial api to not block work on submitter and reporter
290435a
to
183c5ec
Compare
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.
Thanks for the clarification and addressing the comments! There is no blocker from my side, except the suggestion on including the header to the checksum. No strong objection here though.
// Each checkpoint is composed of two parts | ||
NumberOfParts = 2 | ||
|
||
hashLength = 10 |
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 for linkage, right? Better to have a description.
|
||
BlsSigLength = 48 | ||
|
||
// 8 bytes are for 64bit unsigned epoch number |
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 thought we have agreed the epoch number is 4 bytes? See here.
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.
yup, but I saw that epoch number in checkpoint is uin64 - https://github.com/babylonchain/babylon/blob/main/proto/babylon/checkpointing/checkpoint.proto#L14, and I would avoid downcasting to uint32. So the question is wether epoch number should be uint32 or uint64, at checkpoint level, formater part should match this definition.
return data | ||
} | ||
|
||
func u64ToBEBytes(u uint64) []byte { |
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 we use 4 bytes for epoch, we should use uint32, right?
return bytes | ||
} | ||
|
||
func encodeFirstOpRetrun( |
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 we need to include linkage hash here?
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 only add linkage hash to the second op return. (as currently stand there is even no space in the first part fo the linkage hash)
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.
Sorry for the late reply. Very good job! Thanks for providing this formatter. Just left some comments about some inconsistencies with the previous meeting. Please have a check
if len(blsSig) != BlsSigLength { | ||
return nil, nil, errors.New("BlsSig should have 48 bytes") | ||
} |
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.
Duplicated check.
* slashing tx validation * fix * fix * fix * fix * fix check * fix tests and checks * fix * fix checks * fix data types * fix * update e2e tests * fix comments * fix comments * fix comments * fix comments * change to slashing rate * fix comments and checks * fix checks * hardcode slashing rate to 100% to pass tests * fix docstring * more comments * add slashing rate check in checktransactions * change staker addr to change addr * pass change addr in msgcreatedelegation * change address in msgbtcundelegate * error handling for change addr * verbose errors * fix fee checks and validation for slashing tx on rounded slashing rate * more comments * fix docstring * more comments in fee checks * remove staking output info in msgundelegate * remove receiver func Validate * fix tests and include change address in MsgCreateBTCDelegation request * fix tests and pass change address in MsgBTCUndelegate * fix naming and remove dead code * fix e2e tests - pass change addr in types.MsgCreateBTCDelegation * fix imports * fix import issue * fix imports * fix imports * fix * fix args in tx * fix imports * change address in undelegation and delegation creation * keep Validate func * add more comments * fix proto comments * fix slashing rate validation * fix validation * more comments * fix checks in validation and don't depend on change address * remove change address proto * remove change address usages * fix receiver func * remove todos * fix args * fix func sigs * fix imports * fix defs * fix imports * fix formatting * change to ceil(round up) * lock time 0 * fix checks * move fee checks * fix doc string * convert to btcutil.amount * dust relay tx fee * fix dust fn * fix mod * add todo * fix docstring
Initial implementation of formatter with only initial Api
Encode
- which takes all data necessary to create our checkpoint and splits into two byte arrays, attaches appropriate headers, and caluclates sha256 of first halfGetCheckpointData
- which acts as decode, it takes expected tag, half number, version, and return dataGetCheckpointData
can act as filter of of incoming transaction i.e if it do not return error, it indicates that bytes could be valid babylon tx (i.e has correct length and correct header)Implementation is naive performance wise as it usses append to allocate byte arrays but it can be improved at any time.
Also There is only one test checking happy case, more can be added in subsequent prs.
@gitferry @SebastianElvis as you are working on submitter and reporter, would you need any additional apis ?