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

feat: eth_sendRawTransactionConditional #330

Merged
merged 16 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ var (
utils.GpoIgnoreGasPriceFlag,
utils.GpoMinSuggestedPriorityFeeFlag,
utils.RollupSequencerHTTPFlag,
utils.RollupSequencerEnableTxConditionalFlag,
utils.RollupSequencerTxConditionalRateLimitFlag,
utils.RollupHistoricalRPCFlag,
utils.RollupHistoricalRPCTimeoutFlag,
utils.RollupDisableTxPoolGossipFlag,
Expand Down
17 changes: 17 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,18 @@ var (
Category: flags.RollupCategory,
Value: true,
}
RollupSequencerEnableTxConditionalFlag = &cli.BoolFlag{
Name: "rollup.sequencerenabletxconditional",
Usage: "Serve the eth_sendRawTransactionConditional endpoint and apply the conditional constraints on mempool inclusion & block building",
Category: flags.RollupCategory,
Value: false,
}
RollupSequencerTxConditionalRateLimitFlag = &cli.IntFlag{
Name: "rollup.sequencertxconditionalratelimit",
Usage: "Maximum cost -- storage lookups -- allowed for conditional transactions in a given second",
Category: flags.RollupCategory,
Value: 5000,
}

// Metrics flags
MetricsEnabledFlag = &cli.BoolFlag{
Expand Down Expand Up @@ -1717,6 +1729,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(RollupComputePendingBlock.Name) {
cfg.RollupComputePendingBlock = ctx.Bool(RollupComputePendingBlock.Name)
}

// This flag has a default rate limit so always set
cfg.RollupTransactionConditionalRateLimit = ctx.Int(RollupSequencerTxConditionalRateLimitFlag.Name)
}

func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
Expand Down Expand Up @@ -1955,6 +1970,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name)
cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name)
cfg.ApplySuperchainUpgrades = ctx.Bool(RollupSuperchainUpgradesFlag.Name)
cfg.RollupSequencerEnableTxConditional = ctx.Bool(RollupSequencerEnableTxConditionalFlag.Name)

// Override any default configs for hard coded networks.
switch {
case ctx.Bool(MainnetFlag.Name):
Expand Down
34 changes: 34 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,40 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
return false
}

// CheckTransactionConditional validates the account preconditions against the statedb.
//
// NOTE: A lock is not held on the db while the conditional is checked. The caller must
// ensure no state changes occur while this check is executed.
func (s *StateDB) CheckTransactionConditional(cond *types.TransactionConditional) error {
cost := cond.Cost()

// The max cost is an inclusive limit.
if cost > params.TransactionConditionalMaxCost {
hamdiallam marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("conditional cost, %d, exceeded max: %d", cost, params.TransactionConditionalMaxCost)
}

for addr, acct := range cond.KnownAccounts {
hamdiallam marked this conversation as resolved.
Show resolved Hide resolved
if root, isRoot := acct.Root(); isRoot {
storageRoot := s.GetStorageRoot(addr)
if storageRoot == (common.Hash{}) { // if the root is not found, replace with the empty root hash
storageRoot = types.EmptyRootHash
}
if root != storageRoot {
return fmt.Errorf("failed account storage root constraint. Got %s, Expected %s", storageRoot, root)
}
}
if slots, isSlots := acct.Slots(); isSlots {
for key, state := range slots {
accState := s.GetState(addr, key)
if state != accState {
return fmt.Errorf("failed account storage slot key %s constraint. Got %s, Expected %s", key, accState, state)
}
}
}
}
return nil
hamdiallam marked this conversation as resolved.
Show resolved Hide resolved
}

/*
* SETTERS
*/
Expand Down
218 changes: 218 additions & 0 deletions core/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1373,3 +1373,221 @@ func TestStorageDirtiness(t *testing.T) {
state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
}

func TestCheckTransactionConditional(t *testing.T) {
type preAction struct {
Account common.Address
Slots map[common.Hash]common.Hash
}

tests := []struct {
name string
preActions []preAction
cond types.TransactionConditional
valid bool
}{
{
// Clean Prestate, no defined cond
name: "clean prestate",
preActions: []preAction{},
cond: types.TransactionConditional{},
valid: true,
},
hamdiallam marked this conversation as resolved.
Show resolved Hide resolved
{
// Prestate:
// - address(1)
// - bytes32(0): bytes32(1)
// cond:
// - address(1)
// - bytes32(0): bytes32(1)
name: "matching storage slots",
preActions: []preAction{
{
Account: common.Address{19: 1},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
},
},
valid: true,
},
{
// Prestate:
// - address(1)
// - bytes32(0): bytes32(1)
// cond:
// - address(1)
// - bytes32(0): bytes32(2)
name: "mismatched storage slots",
preActions: []preAction{
{
Account: common.Address{19: 1},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 2},
},
},
},
},
valid: false,
},
{
// Clean Prestate
// cond:
// - address(1)
// - emptyRoot
name: "matching storage root",
preActions: []preAction{},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageRoot: &types.EmptyRootHash,
},
},
},
valid: true,
},
{
// Prestate:
// - address(1)
// - bytes32(0): bytes32(1)
// cond:
// - address(1)
// - emptyRoot
name: "mismatched storage root",
preActions: []preAction{
{
Account: common.Address{19: 1},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageRoot: &types.EmptyRootHash,
},
},
},
valid: false,
},
{
// Prestate:
// - address(1)
// - bytes32(0): bytes32(1)
// - address(2)
// - bytes32(0): bytes32(2)
// cond:
// - address(1)
// - bytes32(0): bytes32(1)
// - address(2)
// - bytes32(0): bytes32(2)
name: "multiple matching",
preActions: []preAction{
{
Account: common.Address{19: 1},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
{
Account: common.Address{19: 2},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 2},
},
},
},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
common.Address{19: 2}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 2},
},
},
},
},
valid: true,
},
{
// Prestate:
// - address(1)
// - bytes32(0): bytes32(1)
// - address(2)
// - bytes32(0): bytes32(3)
// cond:
// - address(1)
// - bytes32(0): bytes32(1)
// - address(2)
// - bytes32(0): bytes32(2)
name: "multiple mismatch single",
preActions: []preAction{
{
Account: common.Address{19: 1},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
{
Account: common.Address{19: 2},
Slots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 3},
},
},
},
cond: types.TransactionConditional{
KnownAccounts: map[common.Address]types.KnownAccount{
common.Address{19: 1}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 1},
},
},
common.Address{19: 2}: types.KnownAccount{
StorageSlots: map[common.Hash]common.Hash{
common.Hash{}: common.Hash{31: 2},
},
},
},
},
valid: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
for _, action := range test.preActions {
for key, value := range action.Slots {
state.SetState(action.Account, key, value)
}
}

// write modifications to the trie
state.IntermediateRoot(false)
if err := state.CheckTransactionConditional(&test.cond); err == nil && !test.valid {
t.Errorf("Test %s got unvalid value: want %v, got err %v", test.name, test.valid, err)
}
})
}
}
14 changes: 12 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1733,16 +1733,26 @@ func (pool *LegacyPool) demoteUnexecutables() {
}
pendingNofundsMeter.Mark(int64(len(drops)))

// Drop all transactions that were rejected by the miner
rejectedDrops := list.txs.Filter(func(tx *types.Transaction) bool {
return tx.Rejected()
})
for _, tx := range rejectedDrops {
hash := tx.Hash()
log.Trace("Removed rejected transaction by the miner", "hash", hash)
pool.all.Remove(hash)
hamdiallam marked this conversation as resolved.
Show resolved Hide resolved
}

for _, tx := range invalids {
hash := tx.Hash()
log.Trace("Demoting pending transaction", "hash", hash)

// Internal shuffle shouldn't touch the lookup set.
pool.enqueueTx(hash, tx, false, false)
}
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids) + len(rejectedDrops)))
if pool.locals.contains(addr) {
localGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
localGauge.Dec(int64(len(olds) + len(drops) + len(invalids) + len(rejectedDrops)))
}
// If there's a gap in front, alert (should never happen) and postpone all transactions
if list.Len() > 0 && list.txs.Get(nonce) == nil {
Expand Down
33 changes: 33 additions & 0 deletions core/txpool/legacypool/legacypool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,39 @@ func TestDropping(t *testing.T) {
}
}

// Tests that transactions marked as reject (by the miner in practice)
// are removed from the pool
func TestRejectedDropping(t *testing.T) {
t.Parallel()

pool, key := setupPool()
defer pool.Close()

account := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, account, big.NewInt(1000))

// create some txs. tx0 has a conditional
tx0, tx1 := transaction(0, 100, key), transaction(1, 200, key)

pool.all.Add(tx0, false)
pool.all.Add(tx1, false)
pool.promoteTx(account, tx0.Hash(), tx0)
pool.promoteTx(account, tx1.Hash(), tx1)

// pool state is unchanged
<-pool.requestReset(nil, nil)
if pool.all.Count() != 2 {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2)
}

// tx0 conditional is marked as rejected and should be removed
tx0.SetRejected()
<-pool.requestReset(nil, nil)
if pool.all.Count() != 1 {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 1)
}
}

// Tests that if a transaction is dropped from the current pending pool (e.g. out
// of fund), all consecutive (still valid, but not executable) transactions are
// postponed back into the future queue to prevent broadcasting them.
Expand Down
Loading