-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0d6f9a5
commit 7ab827d
Showing
8 changed files
with
305 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ var ( | |
|
||
type Validator struct { | ||
NodeID ids.NodeID | ||
Weight uint64 | ||
} | ||
|
||
func New[T Tx]( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package dsmr | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"sort" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/utils" | ||
|
||
"github.com/ava-labs/hypersdk/codec" | ||
"github.com/ava-labs/hypersdk/consts" | ||
) | ||
|
||
type weightedValidator struct { | ||
weight uint64 | ||
nodeID ids.NodeID | ||
// set accumulatedWeight up to index in precomputePartition | ||
accumulatedWeight uint64 | ||
} | ||
|
||
type Partition[T Tx] struct { | ||
validators []weightedValidator | ||
totalWeight uint64 | ||
} | ||
|
||
func (w weightedValidator) Compare(o weightedValidator) int { | ||
return bytes.Compare(w.nodeID[:], o.nodeID[:]) | ||
} | ||
|
||
func NewPartition[T Tx](validators []Validator) *Partition[T] { | ||
weightedVdrs := make([]weightedValidator, len(validators)) | ||
for i, vdr := range validators { | ||
weightedVdrs[i] = weightedValidator{ | ||
weight: vdr.Weight, | ||
nodeID: vdr.NodeID, | ||
} | ||
} | ||
|
||
utils.Sort(weightedVdrs) | ||
accumulatedWeight := uint64(0) | ||
for i, weightedVdr := range weightedVdrs { | ||
accumulatedWeight += weightedVdr.weight | ||
weightedVdr.accumulatedWeight = accumulatedWeight | ||
weightedVdrs[i] = weightedVdr | ||
} | ||
return &Partition[T]{ | ||
validators: weightedVdrs, | ||
totalWeight: accumulatedWeight, | ||
} | ||
} | ||
|
||
func calculateSponsorWeightIndex(sponsor codec.Address, totalWeight uint64) uint64 { | ||
return binary.BigEndian.Uint64(sponsor[len(sponsor)-consts.Uint64Len:]) % totalWeight | ||
} | ||
|
||
func (p *Partition[T]) AssignTx(tx T) (ids.NodeID, bool) { | ||
sponsor := tx.GetSponsor() | ||
sponsorWeightIndex := calculateSponsorWeightIndex(sponsor, p.totalWeight) | ||
nodeIDIndex := sort.Search(len(p.validators), func(i int) bool { | ||
return p.validators[i].accumulatedWeight > sponsorWeightIndex | ||
}) | ||
|
||
return p.validators[nodeIDIndex].nodeID, true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package dsmr | ||
|
||
import ( | ||
"encoding/binary" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/utils" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ava-labs/hypersdk/codec" | ||
"github.com/ava-labs/hypersdk/consts" | ||
) | ||
|
||
// createTestPartition creates a precalculated partition with the provided weights | ||
// and generates sorted nodeIDs to match the original ordering of weights | ||
func createTestPartition(weights []uint64) *Partition[tx] { | ||
nodeIDs := make([]ids.NodeID, 0, len(weights)) | ||
for i := 0; i < len(weights); i++ { | ||
nodeIDs = append(nodeIDs, ids.GenerateTestNodeID()) | ||
} | ||
utils.Sort(nodeIDs) | ||
|
||
validators := make([]Validator, len(weights)) | ||
for i, weight := range weights { | ||
validators[i] = Validator{ | ||
Weight: weight, | ||
NodeID: nodeIDs[i], | ||
} | ||
} | ||
return NewPartition[tx](validators) | ||
} | ||
|
||
func createTestPartitionTx(weight uint64) tx { | ||
sponsorAddrID := ids.GenerateTestID() | ||
binary.BigEndian.PutUint64( | ||
sponsorAddrID[len(sponsorAddrID)-consts.Uint64Len:], | ||
weight, | ||
) | ||
|
||
sponsorAddr := codec.CreateAddress(0, sponsorAddrID) | ||
return tx{ | ||
ID: ids.GenerateTestID(), | ||
Expiry: 100, | ||
Sponsor: sponsorAddr, | ||
} | ||
} | ||
|
||
func TestAssignTx(t *testing.T) { | ||
for _, test := range []struct { | ||
name string | ||
weights []uint64 | ||
txWeight uint64 | ||
expectedIndex int | ||
}{ | ||
{ | ||
name: "zero weight", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
}, | ||
txWeight: 0, | ||
expectedIndex: 0, | ||
}, | ||
{ | ||
name: "total weight", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
}, | ||
txWeight: 200, | ||
expectedIndex: 0, | ||
}, | ||
{ | ||
name: "middle of first node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
}, | ||
txWeight: 50, | ||
expectedIndex: 0, | ||
}, | ||
{ | ||
name: "middle of last node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
}, | ||
txWeight: 150, | ||
expectedIndex: 1, | ||
}, | ||
{ | ||
name: "middle of interior node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
100, | ||
}, | ||
txWeight: 150, | ||
expectedIndex: 1, | ||
}, | ||
{ | ||
name: "upper boundary of interior node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
100, | ||
}, | ||
txWeight: 200, | ||
expectedIndex: 2, | ||
}, | ||
{ | ||
name: "lower boundary of interior node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
100, | ||
}, | ||
txWeight: 100, | ||
expectedIndex: 1, | ||
}, | ||
{ | ||
name: "upper boundary of first node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
100, | ||
}, | ||
txWeight: 100, | ||
expectedIndex: 1, | ||
}, | ||
{ | ||
name: "lower boundary of last node", | ||
weights: []uint64{ | ||
100, | ||
100, | ||
100, | ||
}, | ||
txWeight: 200, | ||
expectedIndex: 2, | ||
}, | ||
} { | ||
t.Run(test.name, func(t *testing.T) { | ||
r := require.New(t) | ||
partition := createTestPartition(test.weights) | ||
|
||
tx := createTestPartitionTx(test.txWeight) | ||
nodeID, ok := partition.AssignTx(tx) | ||
r.True(ok) | ||
foundNodeIDIndex := -1 | ||
for i, vdr := range partition.validators { | ||
if vdr.nodeID == nodeID { | ||
foundNodeIDIndex = i | ||
break | ||
} | ||
} | ||
r.Equal(test.expectedIndex, foundNodeIDIndex) | ||
r.Equal(partition.validators[test.expectedIndex].nodeID, nodeID) | ||
}) | ||
} | ||
} | ||
|
||
// Running tool: /usr/local/go/bin/go test -benchmem -run=^$ -bench ^BenchmarkAssignTx$ github.com/ava-labs/hypersdk/x/dsmr | ||
// | ||
// goos: darwin | ||
// goarch: arm64 | ||
// pkg: github.com/ava-labs/hypersdk/x/dsmr | ||
// BenchmarkAssignTx/Vdrs-10-Txs-10-12 332629 3400 ns/op 1792 B/op 1 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10-Txs-100-12 36576 32696 ns/op 14360 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10-Txs-1000-12 3703 325971 ns/op 229401 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10-Txs-10000-12 356 3234778 ns/op 1777688 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-100-Txs-10-12 324730 3539 ns/op 1812 B/op 1 allocs/op | ||
// BenchmarkAssignTx/Vdrs-100-Txs-100-12 34230 34997 ns/op 14378 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-100-Txs-1000-12 3499 341810 ns/op 229400 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-100-Txs-10000-12 355 3340795 ns/op 1777688 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-1000-Txs-10-12 314222 3685 ns/op 1812 B/op 1 allocs/op | ||
// BenchmarkAssignTx/Vdrs-1000-Txs-100-12 33060 36677 ns/op 15559 B/op 6 allocs/op | ||
// BenchmarkAssignTx/Vdrs-1000-Txs-1000-12 3241 368321 ns/op 229408 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-1000-Txs-10000-12 327 3657135 ns/op 1777688 B/op 2 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10000-Txs-10-12 295610 3920 ns/op 1812 B/op 1 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10000-Txs-100-12 30014 39876 ns/op 15843 B/op 6 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10000-Txs-1000-12 2899 416381 ns/op 229482 B/op 5 allocs/op | ||
// BenchmarkAssignTx/Vdrs-10000-Txs-10000-12 286 4145923 ns/op 1777898 B/op 7 allocs/op | ||
func BenchmarkAssignTx(b *testing.B) { | ||
vdrSetSizes := []int{10, 100, 1000, 10_000} | ||
txBatchSizes := []int{10, 100, 1000, 10_000} | ||
for _, numVdrs := range vdrSetSizes { | ||
// Create validator set with equal weights | ||
vdrWeights := make([]uint64, numVdrs) | ||
for i := 0; i < numVdrs; i++ { | ||
vdrWeights[i] = 100 | ||
} | ||
partition := createTestPartition(vdrWeights) | ||
|
||
// Create txs once for largest batch size | ||
testTxs := make([]tx, txBatchSizes[len(txBatchSizes)-1]) | ||
for i := range testTxs { | ||
testTxs[i] = tx{ | ||
ID: ids.GenerateTestID(), | ||
Expiry: 100, | ||
Sponsor: codec.CreateAddress(0, ids.GenerateTestID()), | ||
} | ||
} | ||
|
||
for _, numTxs := range txBatchSizes { | ||
b.Run("Vdrs-"+strconv.Itoa(numVdrs)+"-Txs-"+strconv.Itoa(numTxs), func(b *testing.B) { | ||
r := require.New(b) | ||
for n := 0; n < b.N; n++ { | ||
assignments := make(map[ids.NodeID]tx, numTxs) | ||
for _, tx := range testTxs[:numTxs] { | ||
nodeID, ok := partition.AssignTx(tx) | ||
r.True(ok) | ||
assignments[nodeID] = tx | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} |