Skip to content

Commit e2f0dff

Browse files
Group operations
1 parent 02e9a00 commit e2f0dff

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

parser/group_operations.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package parser
2+
3+
import (
4+
"github.com/coinbase/rosetta-sdk-go/types"
5+
)
6+
7+
// OperationGroup is a group of related operations
8+
// If all operations in a group have the same operation.Type,
9+
// the Type is also populated.
10+
type OperationGroup struct {
11+
Type string
12+
Operations []*types.Operation
13+
}
14+
15+
func containsInt(valid []int, value int) bool {
16+
for _, v := range valid {
17+
if v == value {
18+
return true
19+
}
20+
}
21+
22+
return false
23+
}
24+
25+
// GroupOperations parses all of a transaction's opertations and returns a slice
26+
// of each group of related operations. This should ONLY be called on operations
27+
// that have already been asserted for correctness. Assertion ensures there are
28+
// no duplicate operation indexes, operations are sorted, and that operations
29+
// only reference operations with an index less than theirs.
30+
func GroupOperations(transaction *types.Transaction) []*OperationGroup {
31+
ops := transaction.Operations
32+
opGroups := map[int]*OperationGroup{} // using a map makes group merges much easier
33+
opAssignments := make([]int, len(ops))
34+
for i, op := range ops {
35+
if len(op.RelatedOperations) == 0 {
36+
opGroups[len(opGroups)] = &OperationGroup{
37+
Type: op.Type,
38+
Operations: []*types.Operation{op},
39+
}
40+
opAssignments[i] = len(opGroups)
41+
continue
42+
}
43+
44+
// Find groups to merge
45+
groupsToMerge := []int{}
46+
for _, relatedOp := range op.RelatedOperations {
47+
if !containsInt(groupsToMerge, opAssignments[relatedOp.Index]) {
48+
groupsToMerge = append(groupsToMerge, opAssignments[relatedOp.Index])
49+
}
50+
}
51+
52+
// Merge Groups
53+
mergedGroupIndex := groupsToMerge[0]
54+
mergedGroup := opGroups[mergedGroupIndex]
55+
for _, otherGroupIndex := range groupsToMerge[1:] {
56+
otherGroup := opGroups[otherGroupIndex]
57+
58+
// Add otherGroup ops to mergedGroup
59+
for _, otherOp := range otherGroup.Operations {
60+
if otherOp.Type != mergedGroup.Type && mergedGroup.Type != "" {
61+
mergedGroup.Type = ""
62+
}
63+
64+
mergedGroup.Operations = append(mergedGroup.Operations, otherOp)
65+
opAssignments[otherOp.OperationIdentifier.Index] = mergedGroupIndex
66+
}
67+
68+
// Delete otherGroup
69+
delete(opGroups, otherGroupIndex)
70+
}
71+
}
72+
73+
return func() []*OperationGroup {
74+
sliceGroups := []*OperationGroup{}
75+
for _, v := range opGroups {
76+
sliceGroups = append(sliceGroups, v)
77+
}
78+
79+
return sliceGroups
80+
}()
81+
}

parser/group_operations_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package parser
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coinbase/rosetta-sdk-go/types"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestGroupOperations(t *testing.T) {
12+
var tests = map[string]struct {
13+
transaction *types.Transaction
14+
groups []*OperationGroup
15+
}{
16+
"no ops": {
17+
transaction: &types.Transaction{},
18+
groups: []*OperationGroup{},
19+
},
20+
"unrelated ops": {
21+
transaction: &types.Transaction{
22+
Operations: []*types.Operation{
23+
{
24+
OperationIdentifier: &types.OperationIdentifier{
25+
Index: 0,
26+
},
27+
Type: "op 0",
28+
},
29+
{
30+
OperationIdentifier: &types.OperationIdentifier{
31+
Index: 1,
32+
},
33+
Type: "op 1",
34+
},
35+
{
36+
OperationIdentifier: &types.OperationIdentifier{
37+
Index: 2,
38+
},
39+
Type: "op 2",
40+
},
41+
},
42+
},
43+
groups: []*OperationGroup{
44+
{
45+
Type: "op 0",
46+
Operations: []*types.Operation{
47+
{
48+
OperationIdentifier: &types.OperationIdentifier{
49+
Index: 0,
50+
},
51+
Type: "op 0",
52+
},
53+
},
54+
},
55+
{
56+
Type: "op 1",
57+
Operations: []*types.Operation{
58+
{
59+
OperationIdentifier: &types.OperationIdentifier{
60+
Index: 1,
61+
},
62+
Type: "op 1",
63+
},
64+
},
65+
},
66+
{
67+
Type: "op 2",
68+
Operations: []*types.Operation{
69+
{
70+
OperationIdentifier: &types.OperationIdentifier{
71+
Index: 2,
72+
},
73+
Type: "op 2",
74+
},
75+
},
76+
},
77+
},
78+
},
79+
}
80+
81+
for name, test := range tests {
82+
t.Run(name, func(t *testing.T) {
83+
assert.Equal(t, test.groups, GroupOperations(test.transaction))
84+
})
85+
}
86+
}

0 commit comments

Comments
 (0)