11import * as fs from 'fs' ;
2+ import * as crypto from 'crypto' ;
23import * as consts from './consts'
34import { ethers } from "ethers" ;
45import { namedAddress } from './accounts'
6+ import { S3Client , PutObjectCommand , CreateBucketCommand , HeadBucketCommand } from "@aws-sdk/client-s3" ;
57
68const path = require ( "path" ) ;
79
10+ const S3_CONFIG = {
11+ endpoint : "http://minio:9000" ,
12+ region : "us-east-1" ,
13+ credentials : {
14+ accessKeyId : "minioadmin" ,
15+ secretAccessKey : "minioadmin" ,
16+ } ,
17+ forcePathStyle : true ,
18+ } ;
19+
20+ const S3_BUCKET = "tx-filtering" ;
21+ const S3_OBJECT_KEY = "address-hashes.json" ;
22+
823function writePrysmConfig ( argv : any ) {
924 const prysm = `
1025CONFIG_NAME: interop
@@ -184,6 +199,27 @@ function getChainInfo(): ChainInfo {
184199 return chainInfo ;
185200}
186201
202+ function applyTxFilteringConfig ( config : any ) {
203+ config . execution [ "address-filter" ] = {
204+ "enable" : true ,
205+ "s3" : {
206+ "access-key" : "minioadmin" ,
207+ "secret-key" : "minioadmin" ,
208+ "region" : "us-east-1" ,
209+ "endpoint" : "http://minio:9000" ,
210+ "bucket" : "tx-filtering" ,
211+ "object-key" : "address-hashes.json"
212+ } ,
213+ "poll-interval" : "30s"
214+ } ;
215+ config . execution [ "transaction-filterer-rpc-client" ] = {
216+ "url" : "http://transaction-filterer:8547"
217+ } ;
218+ config [ "init" ] = {
219+ "transaction-filtering-enabled" : true
220+ } ;
221+ }
222+
187223function writeConfigs ( argv : any ) {
188224 const valJwtSecret = path . join ( consts . configpath , "val_jwt.hex" )
189225 const chainInfoFile = path . join ( consts . configpath , "l2_chain_info.json" )
@@ -315,6 +351,9 @@ function writeConfigs(argv: any) {
315351 if ( argv . anytrust ) {
316352 simpleConfig . node [ "data-availability" ] [ "rpc-aggregator" ] . enable = true
317353 }
354+ if ( argv . txfiltering ) {
355+ applyTxFilteringConfig ( simpleConfig ) ;
356+ }
318357 fs . writeFileSync ( path . join ( consts . configpath , "sequencer_config.json" ) , JSON . stringify ( simpleConfig ) )
319358 } else {
320359 let validatorConfig = JSON . parse ( baseConfJSON )
@@ -338,6 +377,9 @@ function writeConfigs(argv: any) {
338377 "redis-url" : argv . redisUrl
339378 } ;
340379 }
380+ if ( argv . txfiltering ) {
381+ applyTxFilteringConfig ( sequencerConfig ) ;
382+ }
341383 fs . writeFileSync ( path . join ( consts . configpath , "sequencer_config.json" ) , JSON . stringify ( sequencerConfig ) )
342384
343385 let posterConfig = JSON . parse ( baseConfJSON )
@@ -418,7 +460,7 @@ function writeL2ChainConfig(argv: any) {
418460 "EnableArbOS" : true ,
419461 "AllowDebugPrecompiles" : true ,
420462 "DataAvailabilityCommittee" : argv . anytrust ,
421- "InitialArbOSVersion" : 40 ,
463+ "InitialArbOSVersion" : argv . txfiltering ? 60 : 40 ,
422464 "InitialChainOwner" : argv . l2owner ,
423465 "GenesisBlockNum" : 0
424466 }
@@ -658,6 +700,11 @@ export const writeConfigCommand = {
658700 describe : "run sequencer in timeboost mode" ,
659701 default : false
660702 } ,
703+ txfiltering : {
704+ boolean : true ,
705+ describe : "enable transaction filtering mode" ,
706+ default : false
707+ } ,
661708 } ,
662709 handler : ( argv : any ) => {
663710 writeConfigs ( argv )
@@ -688,6 +735,11 @@ export const writeL2ChainConfigCommand = {
688735 boolean : true ,
689736 describe : "enable anytrust in chainconfig" ,
690737 default : false
738+ } ,
739+ txfiltering : {
740+ boolean : true ,
741+ describe : "enable transaction filtering (requires ArbOS version 60)" ,
742+ default : false
691743 }
692744 } ,
693745 handler : ( argv : any ) => {
@@ -754,3 +806,176 @@ export const writeL2ReferenceDAConfigCommand = {
754806 writeL2ReferenceDAConfig ( argv )
755807 }
756808}
809+
810+ function writeTransactionFiltererConfig ( ) {
811+ const config = {
812+ "chain-id" : 412346 ,
813+ "sequencer" : {
814+ "url" : "http://sequencer:8547"
815+ } ,
816+ "wallet" : {
817+ "account" : namedAddress ( "filterer" ) ,
818+ "password" : consts . l1passphrase ,
819+ "pathname" : consts . l1keystore
820+ } ,
821+ "http" : {
822+ "addr" : "0.0.0.0" ,
823+ "port" : 8547
824+ }
825+ } ;
826+ fs . writeFileSync ( path . join ( consts . configpath , "transaction_filterer_config.json" ) , JSON . stringify ( config ) ) ;
827+ }
828+
829+ export const writeTransactionFiltererConfigCommand = {
830+ command : "write-tx-filterer-config" ,
831+ describe : "writes transaction-filterer service config file" ,
832+ handler : ( ) => {
833+ writeTransactionFiltererConfig ( )
834+ }
835+ }
836+
837+ export const initTxFilteringMinioCommand = {
838+ command : "init-tx-filtering-minio" ,
839+ describe : "initializes MinIO bucket and empty address hash list" ,
840+ handler : async ( ) => {
841+ const salt = crypto . randomBytes ( 32 ) . toString ( 'hex' ) ;
842+ const initialAddressList = {
843+ "salt" : salt ,
844+ "hashing_scheme" : "Sha256" ,
845+ "address_hashes" : [ ]
846+ } ;
847+ fs . writeFileSync ( path . join ( consts . configpath , "initial_address_hashes.json" ) , JSON . stringify ( initialAddressList , null , 2 ) ) ;
848+ fs . writeFileSync ( path . join ( consts . configpath , "tx_filtering_salt.hex" ) , salt ) ;
849+
850+ const s3Client = new S3Client ( S3_CONFIG ) ;
851+
852+ try {
853+ await s3Client . send ( new HeadBucketCommand ( { Bucket : S3_BUCKET } ) ) ;
854+ console . log ( "Bucket already exists:" , S3_BUCKET ) ;
855+ } catch ( err : any ) {
856+ if ( err . name === "NotFound" || err . $metadata ?. httpStatusCode === 404 ) {
857+ await s3Client . send ( new CreateBucketCommand ( { Bucket : S3_BUCKET } ) ) ;
858+ console . log ( "Created bucket:" , S3_BUCKET ) ;
859+ } else {
860+ throw err ;
861+ }
862+ }
863+
864+ await uploadFilteredAddressesToMinio ( ) ;
865+ console . log ( "Initialized tx-filtering bucket with empty address list." ) ;
866+ }
867+ }
868+
869+ function computeAddressHash ( address : string , salt : string ) : string {
870+ const normalizedAddress = address . toLowerCase ( ) ;
871+ const data = salt + normalizedAddress . replace ( '0x' , '' ) ;
872+ const hash = crypto . createHash ( 'sha256' ) . update ( Buffer . from ( data , 'hex' ) ) . digest ( 'hex' ) ;
873+ return hash ;
874+ }
875+
876+ async function uploadFilteredAddressesToMinio ( ) {
877+ console . log ( "Uploading address list to MinIO..." ) ;
878+ const s3Client = new S3Client ( S3_CONFIG ) ;
879+
880+ const addressListPath = path . join ( consts . configpath , "initial_address_hashes.json" ) ;
881+ const content = fs . readFileSync ( addressListPath ) . toString ( ) ;
882+
883+ await s3Client . send ( new PutObjectCommand ( {
884+ Bucket : S3_BUCKET ,
885+ Key : S3_OBJECT_KEY ,
886+ Body : content ,
887+ ContentType : "application/json" ,
888+ } ) ) ;
889+
890+ console . log ( "Upload complete." ) ;
891+ }
892+
893+ export const hashAddressCommand = {
894+ command : "hash-address" ,
895+ describe : "computes SHA256 hash for an address with salt" ,
896+ builder : {
897+ address : {
898+ string : true ,
899+ describe : "address to hash" ,
900+ demandOption : true
901+ } ,
902+ } ,
903+ handler : ( argv : any ) => {
904+ const saltPath = path . join ( consts . configpath , "tx_filtering_salt.hex" ) ;
905+ if ( ! fs . existsSync ( saltPath ) ) {
906+ console . error ( "Salt file not found. Run init-tx-filtering-minio first." ) ;
907+ process . exit ( 1 ) ;
908+ }
909+ const salt = fs . readFileSync ( saltPath ) . toString ( ) . trim ( ) ;
910+ const hash = computeAddressHash ( argv . address , salt ) ;
911+ console . log ( hash ) ;
912+ }
913+ }
914+
915+ export const addFilteredAddressCommand = {
916+ command : "add-filtered-address" ,
917+ describe : "adds an address hash to the S3 filter list" ,
918+ builder : {
919+ address : {
920+ string : true ,
921+ describe : "address to add to filter list" ,
922+ demandOption : true
923+ } ,
924+ } ,
925+ handler : async ( argv : any ) => {
926+ const saltPath = path . join ( consts . configpath , "tx_filtering_salt.hex" ) ;
927+ if ( ! fs . existsSync ( saltPath ) ) {
928+ console . error ( "Salt file not found. Run init-tx-filtering-minio first." ) ;
929+ process . exit ( 1 ) ;
930+ }
931+ const salt = fs . readFileSync ( saltPath ) . toString ( ) . trim ( ) ;
932+ const hash = computeAddressHash ( argv . address , salt ) ;
933+
934+ const addressListPath = path . join ( consts . configpath , "initial_address_hashes.json" ) ;
935+ const addressList = JSON . parse ( fs . readFileSync ( addressListPath ) . toString ( ) ) ;
936+
937+ const exists = addressList . address_hashes . some ( ( entry : any ) => entry . hash === hash ) ;
938+ if ( ! exists ) {
939+ addressList . address_hashes . push ( { hash : hash } ) ;
940+ fs . writeFileSync ( addressListPath , JSON . stringify ( addressList , null , 2 ) ) ;
941+ console . log ( "Added address hash:" , hash ) ;
942+ await uploadFilteredAddressesToMinio ( ) ;
943+ } else {
944+ console . log ( "Address hash already in list:" , hash ) ;
945+ }
946+ }
947+ }
948+
949+ export const removeFilteredAddressCommand = {
950+ command : "remove-filtered-address" ,
951+ describe : "removes an address hash from the S3 filter list" ,
952+ builder : {
953+ address : {
954+ string : true ,
955+ describe : "address to remove from filter list" ,
956+ demandOption : true
957+ } ,
958+ } ,
959+ handler : async ( argv : any ) => {
960+ const saltPath = path . join ( consts . configpath , "tx_filtering_salt.hex" ) ;
961+ if ( ! fs . existsSync ( saltPath ) ) {
962+ console . error ( "Salt file not found. Run init-tx-filtering-minio first." ) ;
963+ process . exit ( 1 ) ;
964+ }
965+ const salt = fs . readFileSync ( saltPath ) . toString ( ) . trim ( ) ;
966+ const hash = computeAddressHash ( argv . address , salt ) ;
967+
968+ const addressListPath = path . join ( consts . configpath , "initial_address_hashes.json" ) ;
969+ const addressList = JSON . parse ( fs . readFileSync ( addressListPath ) . toString ( ) ) ;
970+
971+ const index = addressList . address_hashes . findIndex ( ( entry : any ) => entry . hash === hash ) ;
972+ if ( index > - 1 ) {
973+ addressList . address_hashes . splice ( index , 1 ) ;
974+ fs . writeFileSync ( addressListPath , JSON . stringify ( addressList , null , 2 ) ) ;
975+ console . log ( "Removed address hash:" , hash ) ;
976+ await uploadFilteredAddressesToMinio ( ) ;
977+ } else {
978+ console . log ( "Address hash not in list:" , hash ) ;
979+ }
980+ }
981+ }
0 commit comments