From ca6db9e8d17a19adc41744c6cd4cc7083277a611 Mon Sep 17 00:00:00 2001 From: Rachit Sonthalia Date: Sat, 1 Jun 2024 13:27:51 +0530 Subject: [PATCH] wip --- go.mod | 2 + go.sum | 4 + turbo/stages/mock/mock_sentry.go | 2 +- zk/txpool/acl.go | 28 ++++++ zk/txpool/policy.go | 129 +++++++++++++++++++++++++ zk/txpool/pool.go | 90 +++++++++++------ zk/txpool/txpooluitl/all_components.go | 12 ++- 7 files changed, 236 insertions(+), 31 deletions(-) create mode 100644 zk/txpool/acl.go create mode 100644 zk/txpool/policy.go diff --git a/go.mod b/go.mod index 8c96581f72a..3c25c6de7f4 100644 --- a/go.mod +++ b/go.mod @@ -169,6 +169,7 @@ require ( github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c // indirect + github.com/gateway-fm/cdk-erigon-lib v0.0.0-20240415152010-66aa405ca33f // indirect github.com/go-llsqlite/adapter v0.0.0-20230912124304-94ed0e573c23 // indirect github.com/go-llsqlite/crawshaw v0.0.0-20230910110433-7e901377eb6c // indirect github.com/go-logr/logr v1.2.4 // indirect @@ -282,6 +283,7 @@ require ( github.com/stoewer/go-strcase v1.2.0 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/torquem-ch/mdbx-go v0.27.10 // indirect github.com/valyala/fastrand v1.1.0 // indirect github.com/valyala/histogram v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect diff --git a/go.sum b/go.sum index 1c1fbd0dce8..d09f2c1f127 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c h1:uYNKzPntb8c6DKvP9EfrBjkLkU7pM4lM+uuHSIa8UtU= github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= +github.com/gateway-fm/cdk-erigon-lib v0.0.0-20240415152010-66aa405ca33f h1:/o6bj7zF7c72CttEswr8gG0iFC749eVprCVc2pKIB9w= +github.com/gateway-fm/cdk-erigon-lib v0.0.0-20240415152010-66aa405ca33f/go.mod h1:Ky//z32wQOTY/drV858kr8dECBD9OBV9Xzy+aG6TxSk= github.com/gateway-fm/vectorized-poseidon-gold v1.0.0 h1:Du0ZW+fkZhgRNGx/gAkHnMj3/Rl8uJkAEe+ZDPX3PDw= github.com/gateway-fm/vectorized-poseidon-gold v1.0.0/go.mod h1:VLGQpyjrOg8+FugH/+d8tfYd/c3z4Xqa+zbUBITygaw= github.com/gballet/go-verkle v0.0.0-20221121182333-31427a1f2d35 h1:I8QswD9gf3VEpr7bpepKKOm7ChxFITIG+oc1I5/S0no= @@ -990,6 +992,8 @@ github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5I github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/torquem-ch/mdbx-go v0.27.10 h1:iwb8Wn9gse4MEYIltAna+pxMPCY7hA1/5LLN/Qrcsx0= +github.com/torquem-ch/mdbx-go v0.27.10/go.mod h1:T2fsoJDVppxfAPTLd1svUgH1kpPmeXdPESmroSHcL1E= github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc= github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= diff --git a/turbo/stages/mock/mock_sentry.go b/turbo/stages/mock/mock_sentry.go index 3e42c96655c..04876b53efc 100644 --- a/turbo/stages/mock/mock_sentry.go +++ b/turbo/stages/mock/mock_sentry.go @@ -318,7 +318,7 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK londonBlock := mock.ChainConfig.LondonBlock shanghaiTime := mock.ChainConfig.ShanghaiTime - mock.TxPool, err = txpool.New(newTxs, mock.DB, txpoolcfg.DefaultConfig, ðconfig.Defaults, kvcache.NewDummy(), *chainID, shanghaiTime, londonBlock) + mock.TxPool, err = txpool.New(newTxs, mock.DB, txpoolcfg.DefaultConfig, ðconfig.Defaults, kvcache.NewDummy(), *chainID, shanghaiTime, londonBlock, nil) if err != nil { tb.Fatal(err) } diff --git a/zk/txpool/acl.go b/zk/txpool/acl.go new file mode 100644 index 00000000000..417c31851ed --- /dev/null +++ b/zk/txpool/acl.go @@ -0,0 +1,28 @@ +package txpool + +import ( + "github.com/ledgerwatch/erigon-lib/kv" +) + +const ( + Whitelist = "Whitelist" + Blacklist = "Blacklist" +) + +var ACLTables = []string{ + Whitelist, + Blacklist, +} + +var ACLTablesCfg = kv.TableCfg{} + +const ACLDB kv.Label = 255 + +func init() { + for _, name := range ACLTables { + _, ok := ACLTablesCfg[name] + if !ok { + ACLTablesCfg[name] = kv.TableCfgItem{} + } + } +} diff --git a/zk/txpool/policy.go b/zk/txpool/policy.go new file mode 100644 index 00000000000..9b303b48883 --- /dev/null +++ b/zk/txpool/policy.go @@ -0,0 +1,129 @@ +package txpool + +import ( + "bytes" + "context" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/types" +) + +// PolicyName is a named policy +type PolicyName string + +const ( + // SendTx is the name of the policy that governs that an address may send transactions to pool + SendTx PolicyName = "send_tx" + // Deploy is the name of the policy that governs that an address may deploy a contract + Deploy PolicyName = "deploy" +) + +// containsPolicy checks if the given policy is present in the policy list +func containsPolicy(policies []byte, policy PolicyName) bool { + return bytes.Contains(policies, []byte(policy)) +} + +// create a method checkpolicy to check an address according to passed policy in the method +func (p *TxPool) checkPolicy(addr common.Address, policy PolicyName) (bool, error) { + var whitelistPolicy []byte + err := p.aclDB.View(context.TODO(), func(tx kv.Tx) error { + value, err := tx.GetOne("Whitelist", addr.Bytes()) + if err != nil { + return err + } + whitelistPolicy = value + return nil + }) + if err != nil { + return false, err + } + if whitelistPolicy != nil && containsPolicy(whitelistPolicy, policy) { + // If address is in the whitelist and has the policy, return true + return true, nil + } + + var blacklistPolicy []byte + err = p.aclDB.View(context.TODO(), func(tx kv.Tx) error { + value, err := tx.GetOne("Blacklist", addr.Bytes()) + if err != nil { + return err + } + blacklistPolicy = value + return nil + }) + if err != nil { + return false, err + } + if blacklistPolicy != nil && containsPolicy(blacklistPolicy, policy) { + // If address is in the blacklist and has the policy, return false + return false, nil + } + + // If the address is not in either list, return false + return false, nil +} + +// create a method to resolve policy which will decode a tx to either sendTx or deploy policy +func resolvePolicy(txn *types.TxSlot) PolicyName { + if txn.Creation { + return Deploy + } + return SendTx +} + +// create a method to setpolicy which will set a policy for an address in the db +func (p *TxPool) setpolicy(addr common.Address, policy PolicyName, bucket string) error { + return p.aclDB.Update(context.TODO(), func(tx kv.RwTx) error { + value, err := tx.GetOne(bucket, addr.Bytes()) + if err != nil { + return err + } + var policies []byte + if value != nil { + policies = value + // Check if the policy already exists + if bytes.Contains(policies, []byte(policy)) { + return nil + } + // Append the new policy + policies = append(policies, byte(',')) + policies = append(policies, []byte(policy)...) + } else { + // New entry + policies = []byte(policy) + } + return tx.Put(bucket, addr.Bytes(), policies) + }) +} + +// method to remove a address from policy +func (p *TxPool) removepolicy(addr common.Address, policy PolicyName, bucket string) error { + return p.aclDB.Update(context.TODO(), func(tx kv.RwTx) error { + value, err := tx.GetOne(bucket, addr.Bytes()) + if err != nil { + return err + } + if value == nil { + // No policies exist for this address + return nil + } + + policies := bytes.Split(value, []byte(",")) + var updatedPolicies [][]byte + + for _, p := range policies { + if string(p) != string(policy) { + updatedPolicies = append(updatedPolicies, p) + } + } + + if len(updatedPolicies) == 0 { + return tx.Delete(bucket, addr.Bytes()) + } + + // Join the updated policies back into a single byte slice + updatedValue := bytes.Join(updatedPolicies, []byte(",")) + return tx.Put(bucket, addr.Bytes(), updatedValue) + }) +} diff --git a/zk/txpool/pool.go b/zk/txpool/pool.go index 3f55b972772..532167c532c 100644 --- a/zk/txpool/pool.go +++ b/zk/txpool/pool.go @@ -116,31 +116,33 @@ const ( type DiscardReason uint8 const ( - NotSet DiscardReason = 0 // analog of "nil-value", means it will be set in future - Success DiscardReason = 1 - AlreadyKnown DiscardReason = 2 - Mined DiscardReason = 3 - ReplacedByHigherTip DiscardReason = 4 - UnderPriced DiscardReason = 5 - ReplaceUnderpriced DiscardReason = 6 // if a transaction is attempted to be replaced with a different one without the required price bump. - FeeTooLow DiscardReason = 7 - OversizedData DiscardReason = 8 - InvalidSender DiscardReason = 9 - NegativeValue DiscardReason = 10 // ensure no one is able to specify a transaction with a negative value. - Spammer DiscardReason = 11 - PendingPoolOverflow DiscardReason = 12 - BaseFeePoolOverflow DiscardReason = 13 - QueuedPoolOverflow DiscardReason = 14 - GasUintOverflow DiscardReason = 15 - IntrinsicGas DiscardReason = 16 - RLPTooLong DiscardReason = 17 - NonceTooLow DiscardReason = 18 - InsufficientFunds DiscardReason = 19 - NotReplaced DiscardReason = 20 // There was an existing transaction with the same sender and nonce, not enough price bump to replace - DuplicateHash DiscardReason = 21 // There was an existing transaction with the same hash - InitCodeTooLarge DiscardReason = 22 // EIP-3860 - transaction init code is too large - UnsupportedTx DiscardReason = 23 // unsupported transaction type - OverflowZkCounters DiscardReason = 24 // unsupported transaction type + NotSet DiscardReason = 0 // analog of "nil-value", means it will be set in future + Success DiscardReason = 1 + AlreadyKnown DiscardReason = 2 + Mined DiscardReason = 3 + ReplacedByHigherTip DiscardReason = 4 + UnderPriced DiscardReason = 5 + ReplaceUnderpriced DiscardReason = 6 // if a transaction is attempted to be replaced with a different one without the required price bump. + FeeTooLow DiscardReason = 7 + OversizedData DiscardReason = 8 + InvalidSender DiscardReason = 9 + NegativeValue DiscardReason = 10 // ensure no one is able to specify a transaction with a negative value. + Spammer DiscardReason = 11 + PendingPoolOverflow DiscardReason = 12 + BaseFeePoolOverflow DiscardReason = 13 + QueuedPoolOverflow DiscardReason = 14 + GasUintOverflow DiscardReason = 15 + IntrinsicGas DiscardReason = 16 + RLPTooLong DiscardReason = 17 + NonceTooLow DiscardReason = 18 + InsufficientFunds DiscardReason = 19 + NotReplaced DiscardReason = 20 // There was an existing transaction with the same sender and nonce, not enough price bump to replace + DuplicateHash DiscardReason = 21 // There was an existing transaction with the same hash + InitCodeTooLarge DiscardReason = 22 // EIP-3860 - transaction init code is too large + UnsupportedTx DiscardReason = 23 // unsupported transaction type + OverflowZkCounters DiscardReason = 24 // unsupported transaction type + SenderDisallowedSendTx DiscardReason = 25 // sender is not allowed to send transactions by ACL policy + SenderDisallowedDeploy DiscardReason = 26 // sender is not allowed to deploy contracts by ACL policy ) func (r DiscardReason) String() string { @@ -195,6 +197,10 @@ func (r DiscardReason) String() string { return "unsupported transaction type" case OverflowZkCounters: return "overflow zk-counters" + case SenderDisallowedSendTx: + return "sender disallowed to send tx by ACL policy" + case SenderDisallowedDeploy: + return "sender disallowed to deploy contract by ACL policy" default: panic(fmt.Sprintf("discard reason: %d", r)) } @@ -303,6 +309,7 @@ type TxPool struct { shanghaiTime *big.Int isPostShanghai atomic.Bool allowFreeTransactions bool + aclDB kv.RwDB // we cannot be in a flushing state whilst getting transactions from the pool, so we have this mutex which is // exposed publicly so anything wanting to get "best" transactions can ensure a flush isn't happening and @@ -310,7 +317,7 @@ type TxPool struct { flushMtx *sync.Mutex } -func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, ethCfg *ethconfig.Config, cache kvcache.Cache, chainID uint256.Int, shanghaiTime *big.Int, londonBlock *big.Int) (*TxPool, error) { +func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, ethCfg *ethconfig.Config, cache kvcache.Cache, chainID uint256.Int, shanghaiTime *big.Int, londonBlock *big.Int, aclDB kv.RwDB) (*TxPool, error) { var err error localsHistory, err := simplelru.NewLRU[string, struct{}](10_000, nil) if err != nil { @@ -352,6 +359,7 @@ func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, shanghaiTime: shanghaiTime, allowFreeTransactions: ethCfg.Zk.AllowFreeTransactions, flushMtx: &sync.Mutex{}, + aclDB: aclDB, }, nil } @@ -643,7 +651,7 @@ func (p *TxPool) AddRemoteTxs(_ context.Context, newTxs types.TxSlots) { } } -func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache.CacheView) DiscardReason { +func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache.CacheView, from common.Address) DiscardReason { isShanghai := p.isShanghai() if isShanghai { if txn.DataLen > fixedgas.MaxInitCodeSize { @@ -704,6 +712,30 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. } return InsufficientFunds } + + // check ACL policy + switch resolvePolicy(txn) { + case SendTx: + var allow bool + allow, err := p.checkPolicy(from, SendTx) + if err != nil { + panic(err) + } + if !allow { + return SenderDisallowedSendTx + } + case Deploy: + var allow bool + // check that sender may deploy contracts + allow, err := p.checkPolicy(from, Deploy) + if err != nil { + panic(err) + } + if !allow { + return SenderDisallowedDeploy + } + } + return Success } @@ -775,7 +807,7 @@ func (p *TxPool) validateTxs(txs *types.TxSlots, stateCache kvcache.CacheView) ( goodCount := 0 for i, txn := range txs.Txs { - reason := p.validateTx(txn, txs.IsLocal[i], stateCache) + reason := p.validateTx(txn, txs.IsLocal[i], stateCache, txs.Senders.AddressAt(i)) if reason == Success { goodCount++ // Success here means no DiscardReason yet, so leave it NotSet @@ -1535,7 +1567,7 @@ func (p *TxPool) fromDB(ctx context.Context, tx kv.Tx, coreTx kv.Tx) error { isLocalTx := p.isLocalLRU.Contains(string(k)) - if reason := p.validateTx(txn, isLocalTx, cacheView); reason != NotSet && reason != Success { + if reason := p.validateTx(txn, isLocalTx, cacheView, addr); reason != NotSet && reason != Success { return nil } txs.Resize(uint(i + 1)) diff --git a/zk/txpool/txpooluitl/all_components.go b/zk/txpool/txpooluitl/all_components.go index e10cd3059be..9f8a1295dc5 100644 --- a/zk/txpool/txpooluitl/all_components.go +++ b/zk/txpool/txpooluitl/all_components.go @@ -112,6 +112,16 @@ func AllComponents(ctx context.Context, cfg txpoolcfg.Config, ethCfg *ethconfig. return nil, nil, nil, nil, nil, err } + aclDB, err := mdbx.NewMDBX(log.New()).Label(txpool.ACLDB).Path(cfg.DBDir). + WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { return txpool.ACLTablesCfg }). + Flags(func(f uint) uint { return f ^ mdbx2.Durable | mdbx2.SafeNoSync }). + GrowthStep(16 * datasize.MB). + SyncPeriod(30 * time.Second). + Open(ctx) + if err != nil { + return nil, nil, nil, nil, nil, err + } + chainConfig, _, err := SaveChainConfigIfNeed(ctx, chainDB, txPoolDB, true) if err != nil { return nil, nil, nil, nil, nil, err @@ -124,7 +134,7 @@ func AllComponents(ctx context.Context, cfg txpoolcfg.Config, ethCfg *ethconfig. shanghaiTime = cfg.OverrideShanghaiTime } - txPool, err := txpool.New(newTxs, chainDB, cfg, ethCfg, cache, *chainID, shanghaiTime, chainConfig.LondonBlock) + txPool, err := txpool.New(newTxs, chainDB, cfg, ethCfg, cache, *chainID, shanghaiTime, chainConfig.LondonBlock, aclDB) if err != nil { return nil, nil, nil, nil, nil, err }