Skip to content

Commit

Permalink
Transactions allow list (#1349)
Browse files Browse the repository at this point in the history
* Implement transactions allow list
  • Loading branch information
vcastellm authored Apr 4, 2023
1 parent afee1c6 commit b48f781
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 7 deletions.
3 changes: 2 additions & 1 deletion chain/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type Params struct {
BlockGasTarget uint64 `json:"blockGasTarget"`

// AllowList configuration
ContractDeployerAllowList *AllowListConfig `json:"contractDeployerAllowListConfig,omitempty"`
ContractDeployerAllowList *AllowListConfig `json:"contractDeployerAllowList,omitempty"`
TransactionsAllowList *AllowListConfig `json:"transactionsAllowList,omitempty"`
}

type AllowListConfig struct {
Expand Down
18 changes: 16 additions & 2 deletions command/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,31 @@ func setFlags(cmd *cobra.Command) {
{
cmd.Flags().StringArrayVar(
&params.contractDeployerAllowListAdmin,
contractDeployedAllowListAdminFlag,
contractDeployerAllowListAdminFlag,
[]string{},
"list of addresses to use as admin accounts in the contract deployer allow list",
)

cmd.Flags().StringArrayVar(
&params.contractDeployerAllowListEnabled,
contractDeployedAllowListEnabledFlag,
contractDeployerAllowListEnabledFlag,
[]string{},
"list of addresses to enable by default in the contract deployer allow list",
)

cmd.Flags().StringArrayVar(
&params.transactionsAllowListAdmin,
transactionsAllowListAdminFlag,
[]string{},
"list of addresses to use as admin accounts in the transactions allow list",
)

cmd.Flags().StringArrayVar(
&params.transactionsAllowListEnabled,
transactionsAllowListEnabledFlag,
[]string{},
"list of addresses to enable by default in the transactions allow list",
)
}
}

Expand Down
5 changes: 4 additions & 1 deletion command/genesis/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ type genesisParams struct {
// allowlist
contractDeployerAllowListAdmin []string
contractDeployerAllowListEnabled []string
mintableNativeToken bool
transactionsAllowListAdmin []string
transactionsAllowListEnabled []string

mintableNativeToken bool
}

func (p *genesisParams) validateFlags() error {
Expand Down
15 changes: 13 additions & 2 deletions command/genesis/polybft_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ const (
defaultBridge = false
defaultEpochReward = 1

contractDeployedAllowListAdminFlag = "contract-deployer-allow-list-admin"
contractDeployedAllowListEnabledFlag = "contract-deployer-allow-list-enabled"
contractDeployerAllowListAdminFlag = "contract-deployer-allow-list-admin"
contractDeployerAllowListEnabledFlag = "contract-deployer-allow-list-enabled"
transactionsAllowListAdminFlag = "transactions-allow-list-admin"
transactionsAllowListEnabledFlag = "transactions-allow-list-enabled"

bootnodePortStart = 30301
)
Expand Down Expand Up @@ -213,6 +215,15 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er
}
}

if len(p.transactionsAllowListAdmin) != 0 {
// only enable allow list if there is at least one address as **admin**, otherwise
// the allow list could never be updated
chainConfig.Params.TransactionsAllowList = &chain.AllowListConfig{
AdminAddresses: stringSliceToAddressSlice(p.transactionsAllowListAdmin),
EnabledAddresses: stringSliceToAddressSlice(p.transactionsAllowListEnabled),
}
}

return helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath)
}

Expand Down
2 changes: 2 additions & 0 deletions contracts/system_addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ var (
ConsolePrecompile = types.StringToAddress("0x000000000000000000636F6e736F6c652e6c6f67")
// AllowListContractsAddr is the address of the contract deployer allow list
AllowListContractsAddr = types.StringToAddress("0x0200000000000000000000000000000000000000")
// AllowListTransactionsAddr is the address of the transactions allow list
AllowListTransactionsAddr = types.StringToAddress("0x0200000000000000000000000000000000000002")
)
91 changes: 91 additions & 0 deletions e2e-polybft/e2e/allowlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,94 @@ func TestAllowList_ContractDeployment(t *testing.T) {
require.Equal(t, role.Uint64(), allowlist.AdminRole.Uint64())
}
}

func TestAllowList_Transactions(t *testing.T) {
// create two accounts, one for an admin sender and a second
// one for a non-enabled account that will switch on-off between
// both enabled and non-enabled roles.
admin, _ := wallet.GenerateKey()
target, _ := wallet.GenerateKey()
other, _ := wallet.GenerateKey()

adminAddr := types.Address(admin.Address())
targetAddr := types.Address(target.Address())
otherAddr := types.Address(other.Address())

cluster := framework.NewTestCluster(t, 3,
framework.WithPremine(adminAddr, targetAddr, otherAddr),
framework.WithTransactionsAllowListAdmin(adminAddr),
framework.WithTransactionsAllowListEnabled(otherAddr),
)
defer cluster.Stop()

cluster.WaitForReady(t)

// bytecode for an empty smart contract
bytecode, _ := hex.DecodeString("6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582027748e4afe5ee282a786005d286f4427f13dac1b62e03f9aed311c2db7e8245364736f6c63430005110032")

expectRole := func(addr types.Address, role allowlist.Role) {
out := cluster.Call(t, contracts.AllowListTransactionsAddr, allowlist.ReadAllowListFunc, addr)

num, ok := out["0"].(*big.Int)
require.True(t, ok)
require.Equal(t, role.Uint64(), num.Uint64())
}

{
// Step 0. Check the role of both accounts
expectRole(adminAddr, allowlist.AdminRole)
expectRole(targetAddr, allowlist.NoRole)
expectRole(otherAddr, allowlist.EnabledRole)
}

{
// Step 1. 'otherAddr' can send a normal transaction (non-contract creation).
otherTxn := cluster.Transfer(t, other, types.ZeroAddress, big.NewInt(1))
require.NoError(t, otherTxn.Wait())
require.True(t, otherTxn.Succeed())
}

{
// Step 2. 'targetAddr' **cannot** send a normal transaction because it is not whitelisted.
targetTxn := cluster.Transfer(t, target, types.ZeroAddress, big.NewInt(1))
require.NoError(t, targetTxn.Wait())
require.True(t, targetTxn.Reverted())
}

{
// Step 2.5. 'targetAddr' **cannot** deploy a contract because it is not whitelisted.
// (The transaction does not fail but the contract is not deployed and all gas
// for the transaction is consumed)
deployTxn := cluster.Deploy(t, target, bytecode)
require.NoError(t, deployTxn.Wait())
require.True(t, deployTxn.Reverted())
require.False(t, cluster.ExistsCode(t, deployTxn.Receipt().ContractAddress))
}

{
// Step 3. 'adminAddr' sends a transaction to enable 'targetAddr'.
input, _ := allowlist.SetEnabledSignatureFunc.Encode([]interface{}{targetAddr})

adminSetTxn := cluster.MethodTxn(t, admin, contracts.AllowListTransactionsAddr, input)
require.NoError(t, adminSetTxn.Wait())
expectRole(targetAddr, allowlist.EnabledRole)
}

{
// Step 4. 'targetAddr' **can** send a normal transaction because it is whitelisted.
targetTxn := cluster.Transfer(t, target, types.ZeroAddress, big.NewInt(1))
require.NoError(t, targetTxn.Wait())
require.True(t, targetTxn.Succeed())
}

{
// Step 5. 'targetAddr' cannot enable other accounts since it is not an admin
// (The transaction fails)
input, _ := allowlist.SetEnabledSignatureFunc.Encode([]interface{}{types.ZeroAddress})

adminSetFailTxn := cluster.MethodTxn(t, target, contracts.AllowListTransactionsAddr, input)
require.NoError(t, adminSetFailTxn.Wait())
require.True(t, adminSetFailTxn.Failed())
expectRole(types.ZeroAddress, allowlist.NoRole)
}
}
24 changes: 24 additions & 0 deletions e2e-polybft/framework/test-cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ type TestClusterConfig struct {

ContractDeployerAllowListAdmin []types.Address
ContractDeployerAllowListEnabled []types.Address
TransactionsAllowListAdmin []types.Address
TransactionsAllowListEnabled []types.Address

NumBlockConfirmations uint64

Expand Down Expand Up @@ -256,6 +258,18 @@ func WithContractDeployerAllowListEnabled(addr types.Address) ClusterOption {
}
}

func WithTransactionsAllowListAdmin(addr types.Address) ClusterOption {
return func(h *TestClusterConfig) {
h.TransactionsAllowListAdmin = append(h.TransactionsAllowListAdmin, addr)
}
}

func WithTransactionsAllowListEnabled(addr types.Address) ClusterOption {
return func(h *TestClusterConfig) {
h.TransactionsAllowListEnabled = append(h.TransactionsAllowListEnabled, addr)
}
}

func isTrueEnv(e string) bool {
return strings.ToLower(os.Getenv(e)) == "true"
}
Expand Down Expand Up @@ -413,6 +427,16 @@ func NewTestCluster(t *testing.T, validatorsCount int, opts ...ClusterOption) *T
strings.Join(sliceAddressToSliceString(cluster.Config.ContractDeployerAllowListEnabled), ","))
}

if len(cluster.Config.TransactionsAllowListAdmin) != 0 {
args = append(args, "--transactions-allow-list-admin",
strings.Join(sliceAddressToSliceString(cluster.Config.TransactionsAllowListAdmin), ","))
}

if len(cluster.Config.TransactionsAllowListEnabled) != 0 {
args = append(args, "--transactions-allow-list-enabled",
strings.Join(sliceAddressToSliceString(cluster.Config.TransactionsAllowListEnabled), ","))
}

// run cmd init-genesis with all the arguments
err = cluster.cmdRun(args...)
require.NoError(t, err)
Expand Down
8 changes: 7 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,18 @@ func NewServer(config *Config) (*Server, error) {
m.executor.GenesisPostHook = factory(m.config.Chain, engineName)
}

// apply allow list genesis data
// apply allow list contracts deployer genesis data
if m.config.Chain.Params.ContractDeployerAllowList != nil {
allowlist.ApplyGenesisAllocs(m.config.Chain.Genesis, contracts.AllowListContractsAddr,
m.config.Chain.Params.ContractDeployerAllowList)
}

// apply transactions execution allow list genesis data
if m.config.Chain.Params.TransactionsAllowList != nil {
allowlist.ApplyGenesisAllocs(m.config.Chain.Genesis, contracts.AllowListTransactionsAddr,
m.config.Chain.Params.TransactionsAllowList)
}

var initialStateRoot = types.ZeroHash

if ConsensusType(engineName) == PolyBFTConsensus {
Expand Down
20 changes: 20 additions & 0 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ func (e *Executor) BeginTxn(
txn.deploymentAllowlist = allowlist.NewAllowList(txn, contracts.AllowListContractsAddr)
}

// enable transactions allow list (if any)
if e.config.TransactionsAllowList != nil {
txn.txnAllowList = allowlist.NewAllowList(txn, contracts.AllowListTransactionsAddr)
}

return txn, nil
}

Expand Down Expand Up @@ -563,6 +568,21 @@ func (t *Transition) run(contract *runtime.Contract, host runtime.Host) *runtime
return t.deploymentAllowlist.Run(contract, host, &t.config)
}

if t.txnAllowList != nil {
if t.txnAllowList.Addr() == contract.CodeAddress {
return t.txnAllowList.Run(contract, host, &t.config)
}

if contract.Caller != contracts.SystemCaller {
txnAllowListRole := t.txnAllowList.GetRole(contract.Caller)
if !txnAllowListRole.Enabled() {
return &runtime.ExecutionResult{
Err: fmt.Errorf("caller not authorized"),
}
}
}
}

// check the precompiles
if t.precompiles.CanRun(contract, host, &t.config) {
return t.precompiles.Run(contract, host, &t.config)
Expand Down

0 comments on commit b48f781

Please sign in to comment.