Skip to content

Commit

Permalink
Add tx partitioner (#1799)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronbuchwald authored Nov 21, 2024
1 parent 0d6f9a5 commit 7ab827d
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 6 deletions.
4 changes: 2 additions & 2 deletions examples/morpheusvm/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,8 @@ github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/thepudds/fzgen v0.4.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw=
github.com/thepudds/fzgen v0.4.2/go.mod h1:kHCWdsv5tdnt32NIHYDdgq083m6bMtaY0M+ipiO9xWE=
github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8=
github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ require (
github.com/subosito/gotenv v1.3.0 // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/thepudds/fzgen v0.4.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,8 @@ github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/thepudds/fzgen v0.4.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw=
github.com/thepudds/fzgen v0.4.2/go.mod h1:kHCWdsv5tdnt32NIHYDdgq083m6bMtaY0M+ipiO9xWE=
github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8=
github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
Expand Down
1 change: 1 addition & 0 deletions x/dsmr/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const InitialChunkSize = 250 * 1024
type Tx interface {
GetID() ids.ID
GetExpiry() int64
GetSponsor() codec.Address
}

type UnsignedChunk[T Tx] struct {
Expand Down
1 change: 1 addition & 0 deletions x/dsmr/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var (

type Validator struct {
NodeID ids.NodeID
Weight uint64
}

func New[T Tx](
Expand Down
9 changes: 7 additions & 2 deletions x/dsmr/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1479,8 +1479,9 @@ func getSignerBitSet(t *testing.T, pChain validators.State, nodeIDs ...ids.NodeI
}

type tx struct {
ID ids.ID `serialize:"true"`
Expiry int64 `serialize:"true"`
ID ids.ID `serialize:"true"`
Expiry int64 `serialize:"true"`
Sponsor codec.Address `serialize:"true"`
}

func (t tx) GetID() ids.ID {
Expand All @@ -1491,6 +1492,10 @@ func (t tx) GetExpiry() int64 {
return t.Expiry
}

func (t tx) GetSponsor() codec.Address {
return t.Sponsor
}

type failVerifier struct{}

func (failVerifier) Verify(Chunk[tx]) error {
Expand Down
68 changes: 68 additions & 0 deletions x/dsmr/partition.go
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
}
223 changes: 223 additions & 0 deletions x/dsmr/partition_test.go
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
}
}
})
}
}
}

0 comments on commit 7ab827d

Please sign in to comment.