Conversation
1508c97 to
e48912b
Compare
a5b4628 to
645b15b
Compare
| func computeFingerprint(rawPubKey []byte) string { | ||
| // HashPurpose.PublicKeyFingerprint id=12 encoded as big-endian int32 (4 bytes) | ||
| var purposeBytes [4]byte | ||
| binary.BigEndian.PutUint32(purposeBytes[:], 12) |
There was a problem hiding this comment.
would be nice to see a defined constant for this "12" purpose and any comment or link to what the purpose means, or if there are others.
Fix canton tests and remove hardcoded package id's from canton transfer creation path
| for _, seed := range []byte{0, 1, 127, 255} { | ||
| key := make([]byte, 32) | ||
| for i := range key { | ||
| key[i] = seed | ||
| } | ||
| addr, err := builder.GetAddressFromPublicKey(key) | ||
| require.NoError(t, err) | ||
| _, fp, err := ParsePartyID(addr) | ||
| require.NoError(t, err) | ||
| require.Equal(t, 68, len(fp), "fingerprint must always be 68 chars") | ||
| expected := map[byte]string{ | ||
| 0: "1220ea618da83b6c6b2c4557ffa17d722045169f52b8f50f3b31fc867e266de7e53d", | ||
| 1: "1220974cb80e78f2fea077628a02faa4c57d68a65036eea27fb3463088a1c8527a99", | ||
| 127: "122087fab42073577d1d066f0cc217b347aedf5a73ee5feaa75d5e96538dad977b91", | ||
| 255: "122044e19d94c296e8397d61e759ff1692e5dff8efbcd70d7a9b4033d8b4a259ccd0", | ||
| } | ||
| require.Equal(t, expected[seed], fp) | ||
| } |
There was a problem hiding this comment.
little messy, please refactor into full table-based test.
| name: "valid - pubkey hex name", | ||
| addr: xc.Address("aabbccdd::" + validFP), | ||
| expectedName: "aabbccdd", | ||
| expectedFP: validFP, |
There was a problem hiding this comment.
expectedFP: "1220" + hex.EncodeToString(make([]byte, 32)), // "1220" + 64 zeros| PreparedTransaction *interactive.PreparedTransaction | ||
| HashingSchemeVersion interactive.HashingSchemeVersion |
There was a problem hiding this comment.
PreparedTransaction *interactive.PreparedTransaction `json:"prepared_transaction"`
HashingSchemeVersion interactive.HashingSchemeVersion `json:"hashing_scheme_version"`| var req interactive.ExecuteSubmissionRequest | ||
| if err := proto.Unmarshal(payload, &req); err != nil { | ||
| return fmt.Errorf("failed to unmarshal Canton execute request: %w", err) | ||
| } | ||
|
|
||
| andWaitReq := &interactive.ExecuteSubmissionAndWaitRequest{ | ||
| PreparedTransaction: req.PreparedTransaction, | ||
| PartySignatures: req.PartySignatures, | ||
| SubmissionId: req.SubmissionId, | ||
| UserId: req.UserId, | ||
| HashingSchemeVersion: req.HashingSchemeVersion, | ||
| } |
There was a problem hiding this comment.
Why unmarshal to ExecuteSubmissionRequest to then convert to ExecuteSubmissionAndWaitRequest?
Wouldn't it be more simple to serialize to ExecuteSubmissionAndWaitRequest originally?
| func extractTransferOutputs(ex *v2.ExercisedEvent, decimals int32) ([]amuletCreation, bool) { | ||
| if !isTransferExercise(ex) { | ||
| return nil, false | ||
| } | ||
|
|
||
| arg := ex.GetChoiceArgument() | ||
| if arg == nil { | ||
| return nil, false | ||
| } | ||
| root := arg.GetRecord() | ||
| if root == nil { | ||
| return nil, false | ||
| } | ||
|
|
||
| transferRecord := findRecordField(root, "transfer") | ||
| if transferRecord == nil { | ||
| return nil, false | ||
| } | ||
|
|
||
| var outputs []*v2.Value | ||
| for _, field := range transferRecord.GetFields() { | ||
| if field.GetLabel() == "outputs" { | ||
| if list := field.GetValue().GetList(); list != nil { | ||
| outputs = list.GetElements() | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if len(outputs) == 0 { | ||
| return nil, false | ||
| } | ||
|
|
||
| parsed := make([]amuletCreation, 0, len(outputs)) | ||
| for _, output := range outputs { | ||
| record := output.GetRecord() | ||
| if record == nil { | ||
| continue | ||
| } | ||
| var receiver string | ||
| var amount xc.AmountBlockchain | ||
| var ok bool | ||
| for _, field := range record.GetFields() { | ||
| switch field.GetLabel() { | ||
| case "receiver": | ||
| receiver = field.GetValue().GetParty() | ||
| case "amount": | ||
| amount, ok = extractNumericValue(field.GetValue(), decimals) | ||
| } | ||
| } | ||
| if receiver == "" || !ok { | ||
| continue | ||
| } | ||
| parsed = append(parsed, amuletCreation{owner: receiver, amount: amount}) | ||
| } | ||
| if len(parsed) == 0 { | ||
| return nil, false | ||
| } | ||
| return parsed, true | ||
| } | ||
|
|
||
| func extractNumericValue(value *v2.Value, decimals int32) (xc.AmountBlockchain, bool) { | ||
| if value == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| numeric := value.GetNumeric() | ||
| if numeric == "" { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| human, err := xc.NewAmountHumanReadableFromStr(numeric) | ||
| if err != nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| return human.ToBlockchain(decimals), true | ||
| } | ||
|
|
||
| func extractTransferFee(ex *v2.ExercisedEvent, decimals int32) (xc.AmountBlockchain, bool) { | ||
| if !isTransferExercise(ex) { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
|
|
||
| result := ex.GetExerciseResult() | ||
| if result == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| record := result.GetRecord() | ||
| if record == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| record = unwrapTransferResultRecord(record) | ||
|
|
||
| if burned, ok := extractBurnedFee(record, decimals); ok { | ||
| return burned, true | ||
| } | ||
| if summaryFee, ok := extractSummaryFee(record, decimals); ok { | ||
| return summaryFee, true | ||
| } | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
|
|
||
| func isTransferExercise(ex *v2.ExercisedEvent) bool { | ||
| tid := ex.GetTemplateId() | ||
| if tid == nil || tid.GetModuleName() != "Splice.AmuletRules" { | ||
| return false | ||
| } | ||
| switch ex.GetChoice() { | ||
| case "AmuletRules_Transfer", "TransferPreapproval_Send": | ||
| return true | ||
| default: | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| func unwrapTransferResultRecord(record *v2.Record) *v2.Record { | ||
| if nested := findRecordField(record, "result"); nested != nil { | ||
| return nested | ||
| } | ||
| return record | ||
| } | ||
|
|
||
| func extractBurnedFee(record *v2.Record, decimals int32) (xc.AmountBlockchain, bool) { | ||
| metaRecord := findRecordField(record, "meta") | ||
| if metaRecord == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| valuesRecord := findRecordField(metaRecord, "values") | ||
| if valuesRecord == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| burnedText, ok := extractTextMapValue(valuesRecord, "splice.lfdecentralizedtrust.org/burned") | ||
| if !ok || burnedText == "" { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| return parseHumanAmountToBlockchain(burnedText, decimals) | ||
| } | ||
|
|
||
| func extractSummaryFee(record *v2.Record, decimals int32) (xc.AmountBlockchain, bool) { | ||
| summaryRecord := findRecordField(record, "summary") | ||
| if summaryRecord == nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
|
|
||
| total := xc.NewAmountBlockchainFromUint64(0) | ||
| found := false | ||
|
|
||
| if senderChangeFeeValue, ok := findValueField(summaryRecord, "senderChangeFee"); ok { | ||
| if fee, ok := extractNumericValue(senderChangeFeeValue, decimals); ok { | ||
| total = total.Add(&fee) | ||
| found = true | ||
| } | ||
| } | ||
| if outputFeesValue, ok := findValueField(summaryRecord, "outputFees"); ok { | ||
| if list := outputFeesValue.GetList(); list != nil { | ||
| for _, elem := range list.GetElements() { | ||
| fee, ok := extractNumericValue(elem, decimals) | ||
| if !ok { | ||
| continue | ||
| } | ||
| total = total.Add(&fee) | ||
| found = true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if !found { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| return total, true | ||
| } | ||
|
|
||
| func findRecordField(record *v2.Record, label string) *v2.Record { | ||
| value, ok := findValueField(record, label) | ||
| if !ok { | ||
| return nil | ||
| } | ||
| return value.GetRecord() | ||
| } | ||
|
|
||
| func findValueField(record *v2.Record, label string) (*v2.Value, bool) { | ||
| if record == nil { | ||
| return nil, false | ||
| } | ||
| for _, field := range record.GetFields() { | ||
| if field.GetLabel() == label { | ||
| return field.GetValue(), true | ||
| } | ||
| } | ||
| return nil, false | ||
| } | ||
|
|
||
| func extractTextMapValue(record *v2.Record, key string) (string, bool) { | ||
| for _, field := range record.GetFields() { | ||
| if field.GetLabel() != key { | ||
| continue | ||
| } | ||
| return field.GetValue().GetText(), true | ||
| } | ||
| return "", false | ||
| } | ||
|
|
||
| func parseHumanAmountToBlockchain(value string, decimals int32) (xc.AmountBlockchain, bool) { | ||
| human, err := xc.NewAmountHumanReadableFromStr(value) | ||
| if err != nil { | ||
| return xc.AmountBlockchain{}, false | ||
| } | ||
| return human.ToBlockchain(decimals), true | ||
| } | ||
|
|
||
| func parseRecoveryLookupId(value string) (int64, string, bool) { | ||
| idx := strings.Index(value, "-") | ||
| if idx <= 0 || idx == len(value)-1 { | ||
| return 0, "", false | ||
| } | ||
| beginExclusive, err := strconv.ParseInt(value[:idx], 10, 64) | ||
| if err != nil { | ||
| return 0, "", false | ||
| } | ||
| return beginExclusive, value[idx+1:], true | ||
| } |
There was a problem hiding this comment.
If you refactor the protobuf to be generated locally, then you can refactor all of these helpers to be local methods on the pb types, to support TxInfo method.
E.g.
path/to/proto/record.pb.go
path/to/proto/record.go // <-- Add our own helpers in our own files like these
| func (client *Client) FetchBlock(ctx context.Context, args *xclient.BlockArgs) (*txinfo.BlockWithTransactions, error) { | ||
| return &txinfo.BlockWithTransactions{}, errors.New("not implemented") | ||
| } |
There was a problem hiding this comment.
Is there any equivalent grouping of transactions on Canton that we could build on? Not a big deal if not.
Perhaps just fetching the latest transaction and reporting a "block" with it? Return not-support/not-implemented error kind if height is specified.
| CreateAccountCallRequired AccountStateEnum = "CreateAccountCallRequired" | ||
| Pending AccountStateEnum = "Pending" | ||
| Created AccountStateEnum = "Created" |
There was a problem hiding this comment.
Use these values for consistency with other cordialsys apis:
inactive // CreateAccountCall required
registering
active
No description provided.