Skip to content
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
6 changes: 3 additions & 3 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ func (bc *BlockChain) TxLookupLimit() uint64 {
return bc.txLookupLimit
}

// MustGetPriorityTransactorsForState receives the priority transactor list appropriate for the current state using the contra
func (bc *BlockChain) MustGetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
// GetPriorityTransactorsForState receives the priority transactor list appropriate for the current state using the contra
func (bc *BlockChain) GetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
blockContext := NewEVMBlockContext(header, bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, state, bc.chainConfig, bc.vmConfig)
return MustGetPriorityTransactors(vmenv)
return GetPriorityTransactors(vmenv)
}

// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent.
Expand Down
138 changes: 110 additions & 28 deletions core/prioritytransactors.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package core

import (
"fmt"
"strings"

"github.com/electroneum/electroneum-sc/accounts/abi"
"github.com/electroneum/electroneum-sc/common"
"github.com/electroneum/electroneum-sc/contracts/prioritytransactors"
"github.com/electroneum/electroneum-sc/core/vm"
"github.com/electroneum/electroneum-sc/log"
"github.com/electroneum/electroneum-sc/params"
)

// GetPriorityTransactors Gets the priority transactor list for the current state using the priority contract address for the block number passed
func MustGetPriorityTransactors(evm *vm.EVM) common.PriorityTransactorMap {
func GetPriorityTransactors(evm *vm.EVM) common.PriorityTransactorMap {
var (
blockNumber = evm.Context.BlockNumber
config = evm.ChainConfig()
Expand All @@ -22,38 +22,120 @@ func MustGetPriorityTransactors(evm *vm.EVM) common.PriorityTransactorMap {
result = make(common.PriorityTransactorMap)
)

if address != (common.Address{}) {
// Check if contract code exists at the address. If it doesn't. We haven't deployed the contract yet, so no error needed.
byteCode := evm.StateDB.GetCode(address)
if len(byteCode) == 0 {
return result
}
// No contract configured => no priority transactors
if address == (common.Address{}) {
return result
}

contractABI, _ := abi.JSON(strings.NewReader(prioritytransactors.ETNPriorityTransactorsInterfaceMetaData.ABI))
input, _ := contractABI.Pack(method)
output, _, err := evm.StaticCall(contract, address, input, params.MaxGasLimit)
// if there is an issue pulling the contract panic as something must be very
// wrong, and we don't want an accidental fork or potentially try again and have
// an incorrect flow
if err != nil {
panic(fmt.Errorf("error getting the priority transactors from the EVM/contract: %s", err))
}
// Contract not deployed yet => no priority transactors (not an error)
byteCode := evm.StateDB.GetCode(address)
if len(byteCode) == 0 {
return result
}

contractABI, err := abi.JSON(strings.NewReader(prioritytransactors.ETNPriorityTransactorsInterfaceMetaData.ABI))
if err != nil {
// ABI parse failure is a software/config issue; safest is disable feature rather than crash the chain
log.Error("PriorityTransactors: failed to parse ABI; disabling priority list for this block",
"err", err, "address", address, "block", blockNumber)
return result
}

input, err := contractABI.Pack(method)
if err != nil {
log.Error("PriorityTransactors: failed to pack call data; disabling priority list for this block",
"err", err, "address", address, "block", blockNumber)
return result
}

output, _, err := evm.StaticCall(contract, address, input, params.MaxGasLimit)
if err != nil {
// IMPORTANT: on failure, return empty list (deny privileges) instead of panicking (halting chain)
log.Error("PriorityTransactors: contract call failed; disabling priority list for this block",
"err", err, "address", address, "block", blockNumber)
return result
}

unpackResult, err := contractABI.Unpack(method, output)
if err != nil {
log.Error("PriorityTransactors: ABI unpack failed; disabling priority list for this block",
"err", err, "address", address, "block", blockNumber)
return result
}

// Defensive: avoid panic if return shape unexpected
if len(unpackResult) == 0 {
log.Error("PriorityTransactors: empty return data; disabling priority list for this block",
"address", address, "block", blockNumber)
return result
}

unpackResult, err := contractABI.Unpack(method, output)
// if there is an issue pulling the contract panic as something must be very
// wrong, and we don't want an accidental fork or potentially try again and have
// an incorrect flow
if err != nil {
panic(fmt.Errorf("error getting the priority transactors from the EVM/contract: %s", err))
metas, ok := safeConvertTransactorsMeta(unpackResult[0])
if !ok {
log.Error("PriorityTransactors: unexpected return type; disabling priority list for this block",
"address", address, "block", blockNumber)
return result
}

for _, t := range metas {
// Validate public key bytes are exactly 65 bytes (your PublicKey type length).
pkBytes := common.FromHex(t.PublicKey)
if len(pkBytes) != common.PublicKeyLength {
log.Warn("PriorityTransactors: invalid public key length in contract data; skipping entry",
"len", len(pkBytes),
"name", t.Name,
"address", address,
"block", blockNumber)
continue
}

transactorsMeta := abi.ConvertType(unpackResult[0], new([]prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta)).(*[]prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta)
for _, t := range *transactorsMeta {
result[common.HexToPublicKey(t.PublicKey)] = common.PriorityTransactor{
IsGasPriceWaiver: t.IsGasPriceWaiver,
EntityName: t.Name,
// Optional: reject all-zero pubkeys (prevents silly/garbage entries)
allZero := true
for _, b := range pkBytes {
if b != 0 {
allZero = false
break
}
}
if allZero {
log.Warn("PriorityTransactors: all-zero public key in contract data; skipping entry",
"name", t.Name,
"address", address,
"block", blockNumber)
continue
}

pk := common.BytesToPublicKey(pkBytes)
if !pk.IsValid() {
log.Warn("PriorityTransactors: public key not on secp256k1 curve; skipping entry",
"name", t.Name,
"address", address,
"block", blockNumber)
continue
}
result[pk] = common.PriorityTransactor{
IsGasPriceWaiver: t.IsGasPriceWaiver,
EntityName: t.Name,
}
}

return result
}

func safeConvertTransactorsMeta(unpack0 any) (metas []prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta, ok bool) {
defer func() {
if r := recover(); r != nil {
ok = false
}
}()

ptr := abi.ConvertType(
unpack0,
new([]prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta),
).(*[]prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta)

if ptr == nil {
return nil, false
}
return *ptr, true
}
144 changes: 144 additions & 0 deletions core/prioritytransactors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package core

import (
"testing"

"github.com/electroneum/electroneum-sc/common"
"github.com/electroneum/electroneum-sc/contracts/prioritytransactors"
)

// Test that safeConvertTransactorsMeta returns ok=true and the correct slice
// when given a valid []ETNPriorityTransactorsInterfaceTransactorMeta value.
func TestSafeConvertTransactorsMeta_ValidInput(t *testing.T) {
input := []prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta{
{
IsGasPriceWaiver: true,
PublicKey: "0x04abcd",
Name: "TestEntity",
},
{
IsGasPriceWaiver: false,
PublicKey: "0x04ef01",
Name: "AnotherEntity",
},
}

metas, ok := safeConvertTransactorsMeta(input)
if !ok {
t.Fatal("expected ok=true for valid input")
}
if len(metas) != 2 {
t.Fatalf("expected 2 metas, got %d", len(metas))
}
if metas[0].Name != "TestEntity" {
t.Errorf("expected Name 'TestEntity', got %q", metas[0].Name)
}
if !metas[0].IsGasPriceWaiver {
t.Error("expected IsGasPriceWaiver=true for first entry")
}
if metas[1].Name != "AnotherEntity" {
t.Errorf("expected Name 'AnotherEntity', got %q", metas[1].Name)
}
if metas[1].IsGasPriceWaiver {
t.Error("expected IsGasPriceWaiver=false for second entry")
}
}

// Test that safeConvertTransactorsMeta returns ok=true and an empty slice
// when given a valid but empty slice.
func TestSafeConvertTransactorsMeta_EmptySlice(t *testing.T) {
input := []prioritytransactors.ETNPriorityTransactorsInterfaceTransactorMeta{}

metas, ok := safeConvertTransactorsMeta(input)
if !ok {
t.Fatal("expected ok=true for empty slice input")
}
if len(metas) != 0 {
t.Fatalf("expected 0 metas, got %d", len(metas))
}
}

// Test that safeConvertTransactorsMeta returns ok=false and recovers without
// panicking when given nil input.
func TestSafeConvertTransactorsMeta_NilInput(t *testing.T) {
metas, ok := safeConvertTransactorsMeta(nil)
if ok {
t.Fatal("expected ok=false for nil input")
}
if metas != nil {
t.Fatalf("expected nil metas for nil input, got %v", metas)
}
}

// Test that safeConvertTransactorsMeta returns ok=false and recovers without
// panicking when given an incompatible type (string instead of the expected slice).
func TestSafeConvertTransactorsMeta_WrongType(t *testing.T) {
metas, ok := safeConvertTransactorsMeta("not a slice")
if ok {
t.Fatal("expected ok=false for wrong type input")
}
if metas != nil {
t.Fatalf("expected nil metas for wrong type, got %v", metas)
}
}

// Test that safeConvertTransactorsMeta returns ok=false and recovers without
// panicking when given an integer (another incompatible type).
func TestSafeConvertTransactorsMeta_IntType(t *testing.T) {
metas, ok := safeConvertTransactorsMeta(42)
if ok {
t.Fatal("expected ok=false for int input")
}
if metas != nil {
t.Fatalf("expected nil metas for int input, got %v", metas)
}
}

// Test that safeConvertTransactorsMeta returns ok=false and recovers without
// panicking when given an empty struct (incompatible type).
func TestSafeConvertTransactorsMeta_EmptyStruct(t *testing.T) {
metas, ok := safeConvertTransactorsMeta(struct{}{})
if ok {
t.Fatal("expected ok=false for empty struct input")
}
if metas != nil {
t.Fatalf("expected nil metas for struct input, got %v", metas)
}
}

// Test that PublicKey.IsValid rejects a 65-byte key that starts with 0x04
// but whose (x, y) coordinates do not lie on the secp256k1 curve.
func TestPublicKeyIsValid_InvalidCurvePoint(t *testing.T) {
// 65 bytes starting with 0x04, but x=1, y=1 is not on secp256k1
var pk common.PublicKey
pk[0] = 0x04
pk[1] = 0x01 // x = 1
pk[33] = 0x01 // y = 1

if pk.IsValid() {
t.Error("expected IsValid=false for point not on secp256k1 curve")
}
}

// Test that PublicKey.IsValid rejects a 65-byte key that does not start
// with the uncompressed prefix 0x04.
func TestPublicKeyIsValid_WrongPrefix(t *testing.T) {
// Take a known-good key and corrupt the prefix byte
validHex := "04efb99d9860f4dec4cb548a5722c27e9ef58e37fbab9719c5b33d55c216db49311221a01f638ce5f255875b194e0acaa58b19a89d2e56a864427298f826a7f887"
pk := common.HexToPublicKey(validHex)
pk[0] = 0x02 // compressed prefix — invalid for uncompressed key

if pk.IsValid() {
t.Error("expected IsValid=false for key with wrong prefix byte")
}
}

// Test that PublicKey.IsValid accepts a known-good uncompressed secp256k1 key.
func TestPublicKeyIsValid_ValidKey(t *testing.T) {
validHex := "04efb99d9860f4dec4cb548a5722c27e9ef58e37fbab9719c5b33d55c216db49311221a01f638ce5f255875b194e0acaa58b19a89d2e56a864427298f826a7f887"
pk := common.HexToPublicKey(validHex)

if !pk.IsValid() {
t.Error("expected IsValid=true for known-good secp256k1 public key")
}
}
4 changes: 2 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}
blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
statedb.SetPriorityTransactors(MustGetPriorityTransactors(vmenv))
statedb.SetPriorityTransactors(GetPriorityTransactors(vmenv))
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
Expand Down Expand Up @@ -151,7 +151,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
// Update the priority transactor map for the next tx application in the block validation loop if this tx
// successfully updated the priority transactor list in the EVM/stateDB.
if msg.To() != nil && *msg.To() == config.GetPriorityTransactorsContractAddress(blockNumber) {
statedb.SetPriorityTransactors(MustGetPriorityTransactors(evm))
statedb.SetPriorityTransactors(GetPriorityTransactors(evm))
}
return receipt, err
}
Expand Down
4 changes: 2 additions & 2 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ type blockChain interface {
GetBlock(hash common.Hash, number uint64) *types.Block
StateAt(root common.Hash) (*state.StateDB, error)
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
MustGetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap
GetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap
}

// TxPoolConfig are the configuration parameters of the transaction pool.
Expand Down Expand Up @@ -1421,7 +1421,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
pool.pendingNonces = newTxNoncer(statedb)
pool.currentMaxGas = newHead.GasLimit

pool.currentPriorityTransactors = pool.chain.MustGetPriorityTransactorsForState(newHead, pool.currentState)
pool.currentPriorityTransactors = pool.chain.GetPriorityTransactorsForState(newHead, pool.currentState)

// Inject any transactions discarded due to reorgs
log.Debug("Reinjecting stale transactions", "count", len(reinject))
Expand Down
2 changes: 1 addition & 1 deletion core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) even
return bc.chainHeadFeed.Subscribe(ch)
}

func (bc *testBlockChain) MustGetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
func (bc *testBlockChain) GetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
priorityPubkeys := []string{
"04efb99d9860f4dec4cb548a5722c27e9ef58e37fbab9719c5b33d55c216db49311221a01f638ce5f255875b194e0acaa58b19a89d2e56a864427298f826a7f887",
"047409b5751867a7a4ac4b3b4358c3f87d97a339b2ab0943217e5dec9aebc10938de4bb7447c26f2eaf4e39417976480b30d2b5c60baccaeb08971840f3bbc282f",
Expand Down
4 changes: 2 additions & 2 deletions miner/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func (bc *testBlockChain) GetPriorityTransactorsCache() common.PriorityTransacto
return common.PriorityTransactorMap{}
}

// MustGetPriorityTransactorsForState receives the priority transactor list appropriate for the current state
func (bc *testBlockChain) MustGetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
// GetPriorityTransactorsForState receives the priority transactor list appropriate for the current state
func (bc *testBlockChain) GetPriorityTransactorsForState(header *types.Header, state *state.StateDB) common.PriorityTransactorMap {
return common.PriorityTransactorMap{}
}

Expand Down
2 changes: 1 addition & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
}
var coalescedLogs []*types.Log

transactors := w.chain.MustGetPriorityTransactorsForState(env.header, env.state)
transactors := w.chain.GetPriorityTransactorsForState(env.header, env.state)
for {
// In the following three cases, we will interrupt the execution of the transaction.
// (1) new head block event arrival, the interrupt signal is 1
Expand Down
Loading