Skip to content
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

kaiax/builder: Introduce TxOrGen #289

Merged
merged 9 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 12 additions & 25 deletions kaiax/builder/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,26 @@
package builder

import (
"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/common"
)

type Bundle struct {
// each element can be either *types.Transaction, or TxGenerator
BundleTxs []interface{}
BundleTxs []*TxOrGen

// BundleTxs is placed AFTER the target tx. If empty hash, it is placed at the very front.
TargetTxHash common.Hash
}

// Has checks if the bundle contains a tx with the given hash.
func (b *Bundle) Has(hash common.Hash) bool {
return b.FindIdx(hash) != -1
func (b *Bundle) Has(txOrGen *TxOrGen) bool {
return b.FindIdx(txOrGen.Id) != -1
}

// FindIdx returns if the bundle contains a tx with the given hash and its index in bundle.
func (b *Bundle) FindIdx(hash common.Hash) int {
func (b *Bundle) FindIdx(id common.Hash) int {
for i, txOrGen := range b.BundleTxs {
if tx, ok := txOrGen.(*types.Transaction); ok && tx.Hash() == hash {
if txOrGen.Id == id {
return i
}
}
Expand All @@ -56,31 +55,19 @@ func (b *Bundle) IsConflict(newBundle *Bundle) bool {
return false
}

// 2-2. Build a map of TxHash -> IndexInBundle
hashes := make(map[common.Hash]int)
for i, txOrGen := range b.BundleTxs {
tx, ok := txOrGen.(*types.Transaction)
if !ok {
continue
// 2-2. Check for overlapping txs
for _, txOrGen := range newBundle.BundleTxs {
if b.Has(txOrGen) {
return true
}
hashes[tx.Hash()] = i
}

// 2-3. Check for TargetTxHash breaking current bundle.
// If newBundle.TargetTxHash is equal to the last tx of current bundle, it is NOT a conflict.
// e.g.) b.txs = [0x1, 0x2] and newBundle's TargetTxHash is 0x2.
if idx, ok := hashes[newBundle.TargetTxHash]; ok && idx != len(b.BundleTxs)-1 {
return true
}

// 2-4. Check for overlapping txs
for _, txOrGen := range newBundle.BundleTxs {
if tx, ok := txOrGen.(*types.Transaction); ok {
if _, has := hashes[tx.Hash()]; has {
return true
}
}
if idx := b.FindIdx(newBundle.TargetTxHash); idx == -1 || idx == len(b.BundleTxs)-1 {
return false
}

return false
return true
}
10 changes: 5 additions & 5 deletions kaiax/builder/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestBundle_IsConflict(t *testing.T) {
}

b0 := &Bundle{
BundleTxs: []interface{}{txs[0], txs[1]},
BundleTxs: NewTxOrGenList(txs[0], txs[1]),
TargetTxHash: common.Hash{},
}
defaultTargetHash := txs[1].Hash()
Expand All @@ -46,7 +46,7 @@ func TestBundle_IsConflict(t *testing.T) {
name: "Same TargetTxHash (empty TargetHash)",
bundle: b0,
newBundle: &Bundle{
BundleTxs: []interface{}{},
BundleTxs: NewTxOrGenList(),
TargetTxHash: common.Hash{},
},
expected: true,
Expand All @@ -55,7 +55,7 @@ func TestBundle_IsConflict(t *testing.T) {
name: "TargetTxHash divides a bundle",
bundle: b0,
newBundle: &Bundle{
BundleTxs: []interface{}{},
BundleTxs: NewTxOrGenList(),
TargetTxHash: txs[0].Hash(),
},
expected: true,
Expand All @@ -64,7 +64,7 @@ func TestBundle_IsConflict(t *testing.T) {
name: "Overlapping BundleTxs",
bundle: b0,
newBundle: &Bundle{
BundleTxs: []interface{}{txs[0]},
BundleTxs: NewTxOrGenList(txs[0]),
TargetTxHash: defaultTargetHash,
},
expected: true,
Expand All @@ -73,7 +73,7 @@ func TestBundle_IsConflict(t *testing.T) {
name: "Non-overlapping BundleTxs",
bundle: b0,
newBundle: &Bundle{
BundleTxs: []interface{}{txs[2], txs[3]},
BundleTxs: NewTxOrGenList(txs[2], txs[3]),
TargetTxHash: defaultTargetHash,
},
expected: false,
Expand Down
143 changes: 44 additions & 99 deletions kaiax/builder/impl/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,28 @@
package impl

import (
"bytes"

"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/crypto"
"github.com/kaiachain/kaia/kaiax/builder"
)

// buildDependencyIndices builds a dependency indices of txs.
// Two txs with the same sender has an edge.
// Two txs in the same bundle has an edge.
func buildDependencyIndices(txs []interface{}, bundles []*builder.Bundle, signer types.Signer) (map[common.Address][]int, map[int][]int, error) {
func buildDependencyIndices(txs []*builder.TxOrGen, bundles []*builder.Bundle, signer types.Signer) (map[common.Address][]int, map[int][]int, error) {
senderToIndices := make(map[common.Address][]int)
bundleToIndices := make(map[int][]int)

for i, txOrGen := range txs {
if tx, ok := txOrGen.(*types.Transaction); ok {
if txOrGen.IsConcreteTx() {
tx, _ := txOrGen.GetTx(0)
from, err := types.Sender(signer, tx)
if err != nil {
return nil, nil, err
}
senderToIndices[from] = append(senderToIndices[from], i)
}
if bundleIdx := FindBundleIdxAsTxOrGen(bundles, txOrGen); bundleIdx != -1 {
if bundleIdx := FindBundleIdx(bundles, txOrGen); bundleIdx != -1 {
bundleToIndices[bundleIdx] = append(bundleToIndices[bundleIdx], i)
}
}
Expand All @@ -50,10 +48,10 @@ func buildDependencyIndices(txs []interface{}, bundles []*builder.Bundle, signer

// IncorporateBundleTx incorporates bundle transactions into the transaction list.
// Caller must ensure that there is no conflict between bundles.
func IncorporateBundleTx(txs []*types.Transaction, bundles []*builder.Bundle) ([]interface{}, error) {
ret := make([]interface{}, len(txs))
for i := range txs {
ret[i] = txs[i]
func IncorporateBundleTx(txs []*types.Transaction, bundles []*builder.Bundle) ([]*builder.TxOrGen, error) {
ret := make([]*builder.TxOrGen, len(txs))
for i, tx := range txs {
ret[i] = builder.NewTxOrGenFromTx(tx)
}

for _, bundle := range bundles {
Expand All @@ -67,8 +65,8 @@ func IncorporateBundleTx(txs []*types.Transaction, bundles []*builder.Bundle) ([
}

// incorporate assumes that `txs` does not contain any bundle transactions.
func incorporate(txs []interface{}, bundle *builder.Bundle) ([]interface{}, error) {
ret := make([]interface{}, 0, len(txs)+len(bundle.BundleTxs))
func incorporate(txs []*builder.TxOrGen, bundle *builder.Bundle) ([]*builder.TxOrGen, error) {
ret := make([]*builder.TxOrGen, 0, len(txs)+len(bundle.BundleTxs))
targetFound := false

// 1. place bundle at the beginning
Expand All @@ -79,29 +77,14 @@ func incorporate(txs []interface{}, bundle *builder.Bundle) ([]interface{}, erro

// 2. place bundle after TargetTxHash
for _, txOrGen := range txs {
switch tx := txOrGen.(type) {
case *types.Transaction:
// if tx-in-bundle, the tx will be appended when target is found.
if bundle.Has(tx.Hash()) {
continue
}
// Because bundle.TargetTxHash cannot be in the bundle, we only check tx-not-in-bundle case.
ret = append(ret, tx)
if tx.Hash() == bundle.TargetTxHash {
targetFound = true
for i, txInBundleI := range bundle.BundleTxs {
switch txInBundle := txInBundleI.(type) {
case *types.Transaction:
ret = append(ret, txInBundleI)
case builder.TxGenerator:
txInBundle.Hash = crypto.Keccak256Hash(bundle.TargetTxHash[:], common.Int64ToByteLittleEndian(uint64(i)))
txInBundleI = txInBundle
ret = append(ret, txInBundleI)
}
}
}
default: // if tx is TxGenerator, unconditionally append
ret = append(ret, txOrGen)
// if tx-in-bundle, the tx will be appended when target is found.
if bundle.Has(txOrGen) {
continue
}
ret = append(ret, txOrGen)
if txOrGen.Id == bundle.TargetTxHash {
targetFound = true
ret = append(ret, bundle.BundleTxs...)
}
}

Expand Down Expand Up @@ -147,50 +130,16 @@ func Filter[T any](slice *[]T, toRemove map[int]bool) []T {
return ret
}

func FindBundleIdx(bundles []*builder.Bundle, tx *types.Transaction) int {
func FindBundleIdx(bundles []*builder.Bundle, txOrGen *builder.TxOrGen) int {
for i, bundle := range bundles {
if bundle.Has(tx.Hash()) {
if bundle.Has(txOrGen) {
return i
}
}
return -1
}

func FindBundleIdxAsTxOrGen(bundles []*builder.Bundle, txOrGen interface{}) int {
for i, bundle := range bundles {
for _, txOrGenInBundle := range bundle.BundleTxs {
if EqualTxOrGen(txOrGenInBundle, txOrGen) {
return i
}
}
}
return -1
}

func EqualTxOrGen(txOrGenIX, txOrGenIY interface{}) bool {
var (
txOrGenXHash common.Hash
txOrGenYHash common.Hash
)

switch txOrGenX := txOrGenIX.(type) {
case *types.Transaction:
txOrGenXHash = txOrGenX.Hash()
case builder.TxGenerator:
txOrGenXHash = txOrGenX.Hash
}

switch txOrGenY := txOrGenIY.(type) {
case *types.Transaction:
txOrGenYHash = txOrGenY.Hash()
case builder.TxGenerator:
txOrGenYHash = txOrGenY.Hash
}

return bytes.Equal(txOrGenXHash.Bytes(), txOrGenYHash.Bytes())
}

func SetCorrectTargetTxHash(bundles []*builder.Bundle, txs []interface{}) []*builder.Bundle {
func SetCorrectTargetTxHash(bundles []*builder.Bundle, txs []*builder.TxOrGen) []*builder.Bundle {
ret := make([]*builder.Bundle, 0)
for _, bundle := range bundles {
bundle.TargetTxHash = FindTargetTxHash(bundle, txs)
Expand All @@ -199,57 +148,54 @@ func SetCorrectTargetTxHash(bundles []*builder.Bundle, txs []interface{}) []*bui
return ret
}

func FindTargetTxHash(bundle *builder.Bundle, txs []interface{}) common.Hash {
// If this is never updated it is expected to return an empty hash.
targetTxHash := common.Hash{}
for i, tx := range txs {
// For index greater than 0, if it can be cast to *types.Transaction then record this as the TargetTxHash.
if i > 0 {
if txTarget, ok := txs[i-1].(*types.Transaction); ok {
targetTxHash = txTarget.Hash()
func FindTargetTxHash(bundle *builder.Bundle, txOrGens []*builder.TxOrGen) common.Hash {
for i := range txOrGens {
if bundle.BundleTxs[0].Equals(txOrGens[i]) {
if i == 0 {
return common.Hash{}
} else {
return txOrGens[i-1].Id
}
}
// If tx is the first tx in the bundle then there is no need to look further.
if EqualTxOrGen(bundle.BundleTxs[0], tx) {
break
}
}
return targetTxHash
return common.Hash{}
}

func ShiftTxs(txs *[]interface{}, num int) {
func ShiftTxs(txs *[]*builder.TxOrGen, num int) {
if len(*txs) <= num {
*txs = (*txs)[:0]
return
}
*txs = (*txs)[num:]
}

func PopTxs(txs *[]interface{}, num int, bundles *[]*builder.Bundle, signer types.Signer) {
if len(*txs) == 0 || num == 0 {
func PopTxs(txOrGens *[]*builder.TxOrGen, num int, bundles *[]*builder.Bundle, signer types.Signer) {
if len(*txOrGens) == 0 || num == 0 {
return
}

senderToIndices, bundleToIndices, err := buildDependencyIndices(*txs, *bundles, signer)
senderToIndices, bundleToIndices, err := buildDependencyIndices(*txOrGens, *bundles, signer)
if err != nil {
logger.Error("Failed to build dependency indices", "err", err)
ShiftTxs(txs, num)
ShiftTxs(txOrGens, num)
return
}

toRemove := make(map[int]bool)
queue := make([]int, 0, num)

for i := 0; i < min(num, len(*txs)); i++ {
for i := 0; i < min(num, len(*txOrGens)); i++ {
toRemove[i] = true
queue = append(queue, i)
}

for len(queue) > 0 {
curIdx := queue[0]
queue = queue[1:]
txOrGen := (*txOrGens)[curIdx]

if tx, ok := (*txs)[curIdx].(*types.Transaction); ok {
if txOrGen.IsConcreteTx() {
tx, _ := txOrGen.GetTx(0)
from, _ := types.Sender(signer, tx)
for _, idx := range senderToIndices[from] {
if idx > curIdx && !toRemove[idx] {
Expand All @@ -258,7 +204,7 @@ func PopTxs(txs *[]interface{}, num int, bundles *[]*builder.Bundle, signer type
}
}
}
if bundleIdx := FindBundleIdxAsTxOrGen(*bundles, (*txs)[curIdx]); bundleIdx != -1 {
if bundleIdx := FindBundleIdx(*bundles, txOrGen); bundleIdx != -1 {
for _, idx := range bundleToIndices[bundleIdx] {
if !toRemove[idx] {
toRemove[idx] = true
Expand All @@ -268,7 +214,7 @@ func PopTxs(txs *[]interface{}, num int, bundles *[]*builder.Bundle, signer type
}
}

newTxs := Filter(txs, toRemove)
newTxs := Filter(txOrGens, toRemove)

bundleIdxToRemove := map[int]bool{}
for bundleIdx, txIndices := range bundleToIndices {
Expand All @@ -282,18 +228,17 @@ func PopTxs(txs *[]interface{}, num int, bundles *[]*builder.Bundle, signer type

newBundles := SetCorrectTargetTxHash(Filter(bundles, bundleIdxToRemove), newTxs)

*txs = newTxs
*txOrGens = newTxs
*bundles = newBundles
}

func ExtractBundlesAndIncorporate(arrayTxs []*types.Transaction, txBundlingModules []builder.TxBundlingModule) ([]interface{}, []*builder.Bundle) {
func ExtractBundlesAndIncorporate(arrayTxs []*types.Transaction, txBundlingModules []builder.TxBundlingModule) ([]*builder.TxOrGen, []*builder.Bundle) {
// Detect bundles and add them to bundles
bundles := []*builder.Bundle{}
flattenedTxs := []interface{}{}
flattenedTxs := []*builder.TxOrGen{}
if txBundlingModules == nil {
for _, tx := range arrayTxs {
var itx interface{} = tx
flattenedTxs = append(flattenedTxs, itx)
flattenedTxs = append(flattenedTxs, builder.NewTxOrGenFromTx(tx))
}
return flattenedTxs, nil
}
Expand Down
Loading