diff --git a/core/transaction/transactionSorter.go b/core/transaction/transactionSorter.go new file mode 100644 index 000000000..e6448aa2e --- /dev/null +++ b/core/transaction/transactionSorter.go @@ -0,0 +1,106 @@ +package transaction + +import ( + "bytes" + "sort" + + "github.com/ElrondNetwork/elrond-go-core/data" + "github.com/ElrondNetwork/elrond-go-core/hashing" +) + +// SortTransactionsBySenderAndNonceWithFrontRunningProtection - sorts the transactions by address and randomness source to protect from front running +func SortTransactionsBySenderAndNonceWithFrontRunningProtection(transactions []data.TransactionHandler, hasher hashing.Hasher, randomness []byte) { + // make sure randomness is 32bytes and uniform + randSeed := hasher.Compute(string(randomness)) + xoredAddresses := make(map[string][]byte) + + for _, tx := range transactions { + xoredBytes := xorBytes(tx.GetSndAddr(), randSeed) + xoredAddresses[string(tx.GetSndAddr())] = hasher.Compute(string(xoredBytes)) + } + + sorter := func(i, j int) bool { + txI := transactions[i] + txJ := transactions[j] + + delta := bytes.Compare(xoredAddresses[string(txI.GetSndAddr())], xoredAddresses[string(txJ.GetSndAddr())]) + if delta == 0 { + delta = int(txI.GetNonce()) - int(txJ.GetNonce()) + } + + return delta < 0 + } + + sort.Slice(transactions, sorter) +} + +// TODO remove duplicated function when will use the version of elrond-go which exports transaction order during processing + +// SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions - sorts the transactions by address and randomness source to protect from front running +func SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions(transactions []data.TransactionHandlerWithGasUsedAndFee, hasher hashing.Hasher, randomness []byte) { + // make sure randomness is 32bytes and uniform + randSeed := hasher.Compute(string(randomness)) + xoredAddresses := make(map[string][]byte) + + for _, tx := range transactions { + xoredBytes := xorBytes(tx.GetSndAddr(), randSeed) + xoredAddresses[string(tx.GetSndAddr())] = hasher.Compute(string(xoredBytes)) + } + + sorter := func(i, j int) bool { + txI := transactions[i] + txJ := transactions[j] + + delta := bytes.Compare(xoredAddresses[string(txI.GetSndAddr())], xoredAddresses[string(txJ.GetSndAddr())]) + if delta == 0 { + delta = int(txI.GetNonce()) - int(txJ.GetNonce()) + } + + return delta < 0 + } + + sort.Slice(transactions, sorter) +} + +// SortTransactionsBySenderAndNonce - sorts the transactions by address without the front running protection +func SortTransactionsBySenderAndNonce(transactions []data.TransactionHandler) { + sorter := func(i, j int) bool { + txI := transactions[i] + txJ := transactions[j] + + delta := bytes.Compare(txI.GetSndAddr(), txJ.GetSndAddr()) + if delta == 0 { + delta = int(txI.GetNonce()) - int(txJ.GetNonce()) + } + + return delta < 0 + } + + sort.Slice(transactions, sorter) +} + +// SortTransactionsBySenderAndNonceExtendedTransactions - sorts the transactions by address without the front running protection +func SortTransactionsBySenderAndNonceExtendedTransactions(transactions []data.TransactionHandlerWithGasUsedAndFee) { + sorter := func(i, j int) bool { + txI := transactions[i] + txJ := transactions[j] + + delta := bytes.Compare(txI.GetSndAddr(), txJ.GetSndAddr()) + if delta == 0 { + delta = int(txI.GetNonce()) - int(txJ.GetNonce()) + } + + return delta < 0 + } + + sort.Slice(transactions, sorter) +} + +// parameters need to be of the same len, otherwise it will panic (if second slice shorter) +func xorBytes(a, b []byte) []byte { + res := make([]byte, len(a)) + for i := range a { + res[i] = a[i] ^ b[i] + } + return res +} diff --git a/core/transaction/transactionSorter_test.go b/core/transaction/transactionSorter_test.go new file mode 100644 index 000000000..3e88e3837 --- /dev/null +++ b/core/transaction/transactionSorter_test.go @@ -0,0 +1,110 @@ +package transaction + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/ElrondNetwork/elrond-go-core/core/mock" + "github.com/ElrondNetwork/elrond-go-core/data" + "github.com/ElrondNetwork/elrond-go-core/data/outport" + "github.com/ElrondNetwork/elrond-go-core/data/transaction" + "github.com/stretchr/testify/assert" +) + +func Test_SortTransactionsBySenderAndNonceWithFrontRunningProtection(t *testing.T) { + randomness := "randomness" + nbSenders := 5 + + hasher := &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + if s == randomness { + return []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + } + + return []byte(s) + }, + } + + usedRandomness := hasher.Compute(randomness) + senders := make([][]byte, 0) + for i := 0; i < nbSenders; i++ { + sender := make([]byte, len(usedRandomness)) + copy(sender, usedRandomness) + sender[len(usedRandomness)-1-i] = 0 + senders = append(senders, sender) + } + + txs := []data.TransactionHandler{ + &transaction.Transaction{Nonce: 1, SndAddr: senders[0]}, + &transaction.Transaction{Nonce: 2, SndAddr: senders[2]}, + &transaction.Transaction{Nonce: 1, SndAddr: senders[2]}, + &transaction.Transaction{Nonce: 2, SndAddr: senders[0]}, + &transaction.Transaction{Nonce: 7, SndAddr: senders[1]}, + &transaction.Transaction{Nonce: 6, SndAddr: senders[1]}, + &transaction.Transaction{Nonce: 1, SndAddr: senders[4]}, + &transaction.Transaction{Nonce: 3, SndAddr: senders[3]}, + &transaction.Transaction{Nonce: 3, SndAddr: senders[2]}, + } + wrappedTxs := make([]data.TransactionHandlerWithGasUsedAndFee, 0, len(txs)) + for _, tx := range txs { + wrappedTxs = append(wrappedTxs, outport.NewTransactionHandlerWithGasAndFee(tx, 0, big.NewInt(0))) + } + + SortTransactionsBySenderAndNonceWithFrontRunningProtection(txs, hasher, []byte(randomness)) + SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions(wrappedTxs, hasher, []byte(randomness)) + + expectedOutput := []string{ + "1 ffffffffffffffffffffffffffffff00", + "2 ffffffffffffffffffffffffffffff00", + "6 ffffffffffffffffffffffffffff00ff", + "7 ffffffffffffffffffffffffffff00ff", + "1 ffffffffffffffffffffffffff00ffff", + "2 ffffffffffffffffffffffffff00ffff", + "3 ffffffffffffffffffffffffff00ffff", + "3 ffffffffffffffffffffffff00ffffff", + "1 ffffffffffffffffffffff00ffffffff", + } + + for i, item := range txs { + assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", item.GetNonce(), hex.EncodeToString(item.GetSndAddr()))) + assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", wrappedTxs[i].GetNonce(), hex.EncodeToString(wrappedTxs[i].GetSndAddr()))) + } +} + +func Test_SortTransactionsBySenderAndNonceLegacy(t *testing.T) { + txs := []data.TransactionHandler{ + &transaction.Transaction{Nonce: 3, SndAddr: []byte("bbbb")}, + &transaction.Transaction{Nonce: 1, SndAddr: []byte("aaaa")}, + &transaction.Transaction{Nonce: 5, SndAddr: []byte("bbbb")}, + &transaction.Transaction{Nonce: 2, SndAddr: []byte("aaaa")}, + &transaction.Transaction{Nonce: 7, SndAddr: []byte("aabb")}, + &transaction.Transaction{Nonce: 6, SndAddr: []byte("aabb")}, + &transaction.Transaction{Nonce: 3, SndAddr: []byte("ffff")}, + &transaction.Transaction{Nonce: 3, SndAddr: []byte("eeee")}, + } + wrappedTxs := make([]data.TransactionHandlerWithGasUsedAndFee, 0, len(txs)) + for _, tx := range txs { + wrappedTxs = append(wrappedTxs, outport.NewTransactionHandlerWithGasAndFee(tx, 0, big.NewInt(0))) + } + + SortTransactionsBySenderAndNonce(txs) + SortTransactionsBySenderAndNonceExtendedTransactions(wrappedTxs) + + expectedOutput := []string{ + "1 aaaa", + "2 aaaa", + "6 aabb", + "7 aabb", + "3 bbbb", + "5 bbbb", + "3 eeee", + "3 ffff", + } + + for i, item := range txs { + assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", item.GetNonce(), string(item.GetSndAddr()))) + assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", wrappedTxs[i].GetNonce(), string(wrappedTxs[i].GetSndAddr()))) + } +} diff --git a/data/api/apiBlock.go b/data/api/apiBlock.go index bcc12de0c..cd7044a40 100644 --- a/data/api/apiBlock.go +++ b/data/api/apiBlock.go @@ -23,6 +23,8 @@ type Block struct { AccumulatedFeesInEpoch string `json:"accumulatedFeesInEpoch,omitempty"` DeveloperFeesInEpoch string `json:"developerFeesInEpoch,omitempty"` Status string `json:"status,omitempty"` + RandSeed string `json:"randSeed,omitempty"` + PrevRandSeed string `json:"prevRandSeed,omitempty"` Timestamp time.Duration `json:"timestamp,omitempty"` NotarizedBlocks []*NotarizedBlock `json:"notarizedBlocks,omitempty"` MiniBlocks []*MiniBlock `json:"miniBlocks,omitempty"` diff --git a/data/interface.go b/data/interface.go index b84d0bade..ee9cb7089 100644 --- a/data/interface.go +++ b/data/interface.go @@ -291,6 +291,8 @@ type TransactionHandlerWithGasUsedAndFee interface { GetGasUsed() uint64 GetFee() *big.Int GetTxHandler() TransactionHandler + SetExecutionOrder(order int) + GetExecutionOrder() int } // Encoder represents a byte slice to string encoder diff --git a/data/outport/dtos.go b/data/outport/dtos.go index cd36752b6..f32188ff6 100644 --- a/data/outport/dtos.go +++ b/data/outport/dtos.go @@ -71,12 +71,14 @@ type HeaderGasConsumption struct { // Pool will hold all types of transaction type Pool struct { - Txs map[string]data.TransactionHandlerWithGasUsedAndFee - Scrs map[string]data.TransactionHandlerWithGasUsedAndFee - Rewards map[string]data.TransactionHandlerWithGasUsedAndFee - Invalid map[string]data.TransactionHandlerWithGasUsedAndFee - Receipts map[string]data.TransactionHandlerWithGasUsedAndFee - Logs []*data.LogData + Txs map[string]data.TransactionHandlerWithGasUsedAndFee + Scrs map[string]data.TransactionHandlerWithGasUsedAndFee + Rewards map[string]data.TransactionHandlerWithGasUsedAndFee + Invalid map[string]data.TransactionHandlerWithGasUsedAndFee + Receipts map[string]data.TransactionHandlerWithGasUsedAndFee + Logs []*data.LogData + ScheduledExecutedSCRSHashesPrevBlock []string + ScheduledExecutedInvalidTxsHashesPrevBlock []string } // ValidatorRatingInfo is a structure containing validator rating information diff --git a/data/outport/txWithFee.go b/data/outport/txWithFee.go index b5bffa89f..d8c71c576 100644 --- a/data/outport/txWithFee.go +++ b/data/outport/txWithFee.go @@ -17,6 +17,7 @@ type FeeInfo struct { type TransactionHandlerWithGasAndFee struct { data.TransactionHandler FeeInfo + ExecutionOrder int } // NewTransactionHandlerWithGasAndFee returns a new instance of transactionHandlerWithGasAndFee which matches the interface @@ -65,6 +66,16 @@ func (t *TransactionHandlerWithGasAndFee) GetTxHandler() data.TransactionHandler return t.TransactionHandler } +// SetExecutionOrder will set the execution order of the TransactionHandler +func (t *TransactionHandlerWithGasAndFee) SetExecutionOrder(order int) { + t.ExecutionOrder = order +} + +// GetExecutionOrder will return the execution order of the TransactionHandler +func (t *TransactionHandlerWithGasAndFee) GetExecutionOrder() int { + return t.ExecutionOrder +} + // WrapTxsMap will wrap the provided transactions map in a map fo transactions with fee and gas used func WrapTxsMap(txs map[string]data.TransactionHandler) map[string]data.TransactionHandlerWithGasUsedAndFee { newMap := make(map[string]data.TransactionHandlerWithGasUsedAndFee, len(txs))