diff --git a/go.mod b/go.mod index 03409ef..32f4214 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/attestantio/go-eth2-client v0.18.4-0.20231012194602-0eff364fec01 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 - github.com/ethereum/go-ethereum v1.13.4 + github.com/ethereum/go-ethereum v1.13.8 github.com/gorilla/mux v1.8.1 github.com/herumi/bls-eth-go-binary v1.31.0 github.com/mashingan/smapping v0.1.19 @@ -26,15 +26,15 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.7.0 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/ethpandaops/ethwallclock v0.3.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect @@ -47,7 +47,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect - github.com/holiman/uint256 v1.2.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -76,13 +76,13 @@ require ( go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/tools v0.15.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect diff --git a/go.sum b/go.sum index cd8baa2..77a65f5 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -41,6 +43,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-kzg-4844 v0.6.1-0.20231019121413-3621cc59f0c7 h1:VpZxBC99nEW8Rkz1EBBf7JmaM20H+ZkSmqdxpYEoXuo= +github.com/crate-crypto/go-kzg-4844 v0.6.1-0.20231019121413-3621cc59f0c7/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,8 +60,14 @@ github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeM github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844 v0.3.2-0.20231019020040-748283cced54 h1:jDyQvQjauRyb7TJAF9W7J3NOjn3ukXahd3l+rd1Fak8= +github.com/ethereum/c-kzg-4844 v0.3.2-0.20231019020040-748283cced54/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= +github.com/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg= +github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= github.com/ethpandaops/ethwallclock v0.3.0 h1:xF5fwtBf+bHFHZKBnwiPFEuelW3sMM7SD3ZNFq1lJY4= github.com/ethpandaops/ethwallclock v0.3.0/go.mod h1:y0Cu+mhGLlem19vnAV2x0hpFS5KZ7oOi2SWYayv9l24= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -122,6 +134,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/huandu/go-clone v1.6.0 h1:HMo5uvg4wgfiy5FoGOqlFLQED/VGRm2D9Pi8g1FXPGc= github.com/huandu/go-clone v1.6.0/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0XLdbUYN5U= @@ -266,17 +280,27 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -293,15 +317,20 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= diff --git a/pkg/coordinator/tasks/generate_blob_transactions/config.go b/pkg/coordinator/tasks/generate_blob_transactions/config.go new file mode 100644 index 0000000..f19a088 --- /dev/null +++ b/pkg/coordinator/tasks/generate_blob_transactions/config.go @@ -0,0 +1,63 @@ +package generateblobtransactions + +import ( + "errors" + "math/big" +) + +type Config struct { + LimitPerBlock int `yaml:"limitPerBlock" json:"limitPerBlock"` + LimitTotal int `yaml:"limitTotal" json:"limitTotal"` + LimitPending int `yaml:"limitPending" json:"limitPending"` + PrivateKey string `yaml:"privateKey" json:"privateKey"` + ChildWallets uint64 `yaml:"childWallets" json:"childWallets"` + WalletSeed string `yaml:"walletSeed" json:"walletSeed"` + + RefillPendingLimit uint64 `yaml:"refillPendingLimit" json:"refillPendingLimit"` + RefillFeeCap *big.Int `yaml:"refillFeeCap" json:"refillFeeCap"` + RefillTipCap *big.Int `yaml:"refillTipCap" json:"refillTipCap"` + RefillAmount *big.Int `yaml:"refillAmount" json:"refillAmount"` + RefillMinBalance *big.Int `yaml:"refillMinBalance" json:"refillMinBalance"` + + BlobSidecars uint64 `yaml:"blobSidecars" json:"blobSidecars"` + BlobFeeCap *big.Int `yaml:"blobFeeCap" json:"blobFeeCap"` + FeeCap *big.Int `yaml:"feeCap" json:"feeCap"` + TipCap *big.Int `yaml:"tipCap" json:"tipCap"` + GasLimit uint64 `yaml:"gasLimit" json:"gasLimit"` + TargetAddress string `yaml:"targetAddress" json:"targetAddress"` + RandomTarget bool `yaml:"randomTarget" json:"randomTarget"` + CallData string `yaml:"callData" json:"callData"` + BlobData string `yaml:"blobData" json:"blobData"` + RandomAmount bool `yaml:"randomAmount" json:"randomAmount"` + Amount *big.Int `yaml:"amount" json:"amount"` + + ClientPattern string `yaml:"clientPattern" json:"clientPattern"` +} + +func DefaultConfig() Config { + return Config{ + RefillPendingLimit: 200, + RefillFeeCap: big.NewInt(500000000000), // 500 Gwei + RefillTipCap: big.NewInt(1000000000), // 1 Gwei + RefillAmount: big.NewInt(1000000000000000000), // 1 ETH + RefillMinBalance: big.NewInt(500000000000000000), // 0.5 ETH + BlobSidecars: 1, + BlobFeeCap: big.NewInt(10000000000), // 10 Gwei + FeeCap: big.NewInt(100000000000), // 100 Gwei + TipCap: big.NewInt(2000000000), // 2 Gwei + GasLimit: 100000, + Amount: big.NewInt(0), + } +} + +func (c *Config) Validate() error { + if c.LimitPerBlock == 0 && c.LimitTotal == 0 && c.LimitPending == 0 { + return errors.New("either limitPerBlock or limitTotal or limitPending must be set") + } + + if c.PrivateKey == "" { + return errors.New("privateKey must be set") + } + + return nil +} diff --git a/pkg/coordinator/tasks/generate_blob_transactions/task.go b/pkg/coordinator/tasks/generate_blob_transactions/task.go new file mode 100644 index 0000000..ec9ca13 --- /dev/null +++ b/pkg/coordinator/tasks/generate_blob_transactions/task.go @@ -0,0 +1,343 @@ +package generateblobtransactions + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/coordinator/wallet/blobtx" + "github.com/holiman/uint256" + "github.com/sirupsen/logrus" +) + +var ( + TaskName = "generate_blob_transactions" + TaskDescriptor = &types.TaskDescriptor{ + Name: TaskName, + Description: "Generates blob transactions and sends them to the network", + Config: DefaultConfig(), + NewTask: NewTask, + } +) + +type Task struct { + ctx *types.TaskContext + options *types.TaskOptions + config Config + logger logrus.FieldLogger + txIndex uint64 + wallet *wallet.Wallet + walletPool *wallet.WalletPool + + targetAddr common.Address + transactionData []byte +} + +func NewTask(ctx *types.TaskContext, options *types.TaskOptions) (types.Task, error) { + return &Task{ + ctx: ctx, + options: options, + logger: ctx.Logger.GetLogger(), + }, nil +} + +func (t *Task) Name() string { + return TaskName +} + +func (t *Task) Description() string { + return TaskDescriptor.Description +} + +func (t *Task) Title() string { + return t.ctx.Vars.ResolvePlaceholders(t.options.Title) +} + +func (t *Task) Config() interface{} { + return t.config +} + +func (t *Task) Logger() logrus.FieldLogger { + return t.logger +} + +func (t *Task) Timeout() time.Duration { + return t.options.Timeout.Duration +} + +func (t *Task) LoadConfig() error { + config := DefaultConfig() + + // parse static config + if t.options.Config != nil { + if err := t.options.Config.Unmarshal(&config); err != nil { + return fmt.Errorf("error parsing task config for %v: %w", TaskName, err) + } + } + + // load dynamic vars + err := t.ctx.Vars.ConsumeVars(&config, t.options.ConfigVars) + if err != nil { + return err + } + + // validate config + if valerr := config.Validate(); valerr != nil { + return valerr + } + + // load wallets + privKey, err := crypto.HexToECDSA(config.PrivateKey) + if err != nil { + return err + } + + if config.ChildWallets == 0 { + t.wallet, err = t.ctx.Scheduler.GetCoordinator().WalletManager().GetWalletByPrivkey(privKey) + if err != nil { + return fmt.Errorf("cannot initialize wallet: %w", err) + } + } else { + t.walletPool, err = t.ctx.Scheduler.GetCoordinator().WalletManager().GetWalletPoolByPrivkey(privKey, config.ChildWallets, config.WalletSeed) + if err != nil { + return fmt.Errorf("cannot initialize wallet pool: %w", err) + } + } + + // parse target addr + if config.TargetAddress != "" { + err = t.targetAddr.UnmarshalText([]byte(config.TargetAddress)) + if err != nil { + return fmt.Errorf("cannot decode execution addr: %w", err) + } + } + + // parse transaction data + if config.CallData != "" { + t.transactionData = common.FromHex(config.CallData) + } + + t.config = config + + return nil +} + +func (t *Task) Execute(ctx context.Context) error { + if t.walletPool != nil { + err := t.ensureChildWalletFunding(ctx) + if err != nil { + t.logger.Infof("failed ensuring child wallet funding: %v", err) + return err + } + + for idx, wallet := range t.walletPool.GetChildWallets() { + t.logger.Infof("wallet #%v: %v [nonce: %v]", idx, wallet.GetAddress().Hex(), wallet.GetNonce()) + } + + go t.runChildWalletFundingRoutine(ctx) + } + + var subscription *execution.Subscription[*execution.Block] + if t.config.LimitPerBlock > 0 { + subscription = t.ctx.Scheduler.GetCoordinator().ClientPool().GetExecutionPool().GetBlockCache().SubscribeBlockEvent(10) + defer subscription.Unsubscribe() + } + + var pendingChan chan bool + + if t.config.LimitPending > 0 { + pendingChan = make(chan bool, t.config.LimitPending) + } + + perBlockCount := 0 + totalCount := 0 + + for { + if pendingChan != nil { + select { + case <-ctx.Done(): + return nil + case pendingChan <- true: + } + } + + txIndex := t.txIndex + t.txIndex++ + + err := t.generateTransaction(ctx, txIndex, func(tx *ethtypes.Transaction, receipt *ethtypes.Receipt) { + if pendingChan != nil { + <-pendingChan + } + + if receipt != nil { + t.logger.Infof("transaction %v confirmed (nonce: %v, status: %v)", tx.Hash().Hex(), tx.Nonce(), receipt.Status) + } else { + t.logger.Infof("transaction %v replaced (nonce: %v)", tx.Hash().Hex(), tx.Nonce()) + } + }) + if err != nil { + t.logger.Errorf("error generating transaction: %v", err.Error()) + } else { + perBlockCount++ + totalCount++ + } + + if t.config.LimitTotal > 0 && totalCount >= t.config.LimitTotal { + break + } + + if t.config.LimitPerBlock > 0 && perBlockCount >= t.config.LimitPerBlock { + // await next block + perBlockCount = 0 + select { + case <-ctx.Done(): + return nil + case <-subscription.Channel(): + } + } else if err := ctx.Err(); err != nil { + return err + } + } + + return nil +} + +func (t *Task) runChildWalletFundingRoutine(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(10 * time.Minute): + err := t.ensureChildWalletFunding(ctx) + if err != nil { + t.logger.Infof("failed ensuring child wallet funding: %v", err) + } + } + } +} + +func (t *Task) ensureChildWalletFunding(ctx context.Context) error { + t.logger.Infof("ensure child wallet funding") + + err := t.walletPool.EnsureFunding(ctx, t.config.RefillMinBalance, t.config.RefillAmount, t.config.RefillFeeCap, t.config.RefillTipCap, t.config.RefillPendingLimit) + if err != nil { + return err + } + + return nil +} + +func (t *Task) generateTransaction(ctx context.Context, transactionIdx uint64, confirmedFn func(tx *ethtypes.Transaction, receipt *ethtypes.Receipt)) error { + txWallet := t.wallet + if t.wallet == nil { + txWallet = t.walletPool.GetNextChildWallet() + } + + blobRef := "identifier,random" + if t.config.BlobData != "" { + blobRef = t.config.BlobData + } + + blobRefs := []string{} + for i := 0; i < int(t.config.BlobSidecars); i++ { + blobRefs = append(blobRefs, blobRef) + } + + blobHashes, blobSidecar, err := blobtx.GenerateBlobSidecar(blobRefs, transactionIdx, 0) + if err != nil { + return err + } + + tx, err := txWallet.BuildTransaction(ctx, func(ctx context.Context, nonce uint64, signer bind.SignerFn) (*ethtypes.Transaction, error) { + toAddr := txWallet.GetAddress() + if t.config.RandomTarget { + addrBytes := make([]byte, 20) + //nolint:errcheck // ignore + rand.Read(addrBytes) + toAddr = common.Address(addrBytes) + } else if t.config.TargetAddress != "" { + toAddr = t.targetAddr + } + + txAmount := new(big.Int).Set(t.config.Amount) + if t.config.RandomAmount { + n, err2 := rand.Int(rand.Reader, txAmount) + if err2 == nil { + txAmount = n + } + } + + txData := []byte{} + if t.transactionData != nil { + txData = t.transactionData + } + + txObj := ðtypes.BlobTx{ + ChainID: uint256.MustFromBig(t.ctx.Scheduler.GetCoordinator().ClientPool().GetExecutionPool().GetBlockCache().GetChainID()), + Nonce: nonce, + BlobFeeCap: uint256.MustFromBig(t.config.BlobFeeCap), + GasTipCap: uint256.MustFromBig(t.config.TipCap), + GasFeeCap: uint256.MustFromBig(t.config.FeeCap), + Gas: t.config.GasLimit, + To: toAddr, + Value: uint256.MustFromBig(txAmount), + Data: txData, + BlobHashes: blobHashes, + Sidecar: blobSidecar, + } + + return ethtypes.NewTx(txObj), nil + }) + if err != nil { + return err + } + + var clients []*execution.Client + + clientPool := t.ctx.Scheduler.GetCoordinator().ClientPool() + + if t.config.ClientPattern == "" { + clients = clientPool.GetExecutionPool().GetReadyEndpoints() + } else { + poolClients := clientPool.GetClientsByNamePatterns([]string{t.config.ClientPattern}) + if len(poolClients) == 0 { + return fmt.Errorf("no client found with pattern %v", t.config.ClientPattern) + } + + clients = make([]*execution.Client, len(poolClients)) + for i, c := range poolClients { + clients[i] = c.ExecutionClient + } + } + + client := clients[transactionIdx%uint64(len(clients))] + + t.logger.WithFields(logrus.Fields{ + "client": client.GetName(), + }).Infof("sending tx %v: %v", transactionIdx, tx.Hash().Hex()) + + err = client.GetRPCClient().SendTransaction(ctx, tx) + if err != nil { + return err + } + + go func() { + receipt, err := txWallet.AwaitTransaction(ctx, tx) + if err != nil { + t.logger.Warnf("failed waiting for tx receipt: %v", err) + } + + confirmedFn(tx, receipt) + }() + + return nil +} diff --git a/pkg/coordinator/tasks/generate_eoa_transactions/config.go b/pkg/coordinator/tasks/generate_eoa_transactions/config.go index 6e0e9b0..64cdc9e 100644 --- a/pkg/coordinator/tasks/generate_eoa_transactions/config.go +++ b/pkg/coordinator/tasks/generate_eoa_transactions/config.go @@ -19,31 +19,31 @@ type Config struct { RefillAmount *big.Int `yaml:"refillAmount" json:"refillAmount"` RefillMinBalance *big.Int `yaml:"refillMinBalance" json:"refillMinBalance"` - LegacyTransactions bool `yaml:"legacyTransactions" json:"legacyTransactions"` - TransactionFeeCap *big.Int `yaml:"transactionFeeCap" json:"transactionFeeCap"` - TransactionTipCap *big.Int `yaml:"transactionTipCap" json:"transactionTipCap"` - TransactionGasLimit uint64 `yaml:"transactionGasLimit" json:"transactionGasLimit"` - TargetAddress string `yaml:"targetAddress" json:"targetAddress"` - RandomTarget bool `yaml:"randomTarget" json:"randomTarget"` - ContractDeployment bool `yaml:"contractDeployment" json:"contractDeployment"` - TransactionData string `yaml:"transactionData" json:"transactionData"` - RandomAmount bool `yaml:"randomAmount" json:"randomAmount"` - TransactionAmount *big.Int `yaml:"transactionAmount" json:"transactionAmount"` + LegacyTxType bool `yaml:"legacyTxType" json:"legacyTxType"` + FeeCap *big.Int `yaml:"feeCap" json:"feeCap"` + TipCap *big.Int `yaml:"tipCap" json:"tipCap"` + GasLimit uint64 `yaml:"gasLimit" json:"gasLimit"` + TargetAddress string `yaml:"targetAddress" json:"targetAddress"` + RandomTarget bool `yaml:"randomTarget" json:"randomTarget"` + ContractDeployment bool `yaml:"contractDeployment" json:"contractDeployment"` + CallData string `yaml:"callData" json:"callData"` + RandomAmount bool `yaml:"randomAmount" json:"randomAmount"` + Amount *big.Int `yaml:"amount" json:"amount"` ClientPattern string `yaml:"clientPattern" json:"clientPattern"` } func DefaultConfig() Config { return Config{ - RefillPendingLimit: 200, - RefillFeeCap: big.NewInt(500000000000), // 500 Gwei - RefillTipCap: big.NewInt(1000000000), // 1 Gwei - RefillAmount: big.NewInt(1000000000000000000), // 1 ETH - RefillMinBalance: big.NewInt(500000000000000000), // 0.5 ETH - TransactionFeeCap: big.NewInt(100000000000), // 100 Gwei - TransactionTipCap: big.NewInt(1000000000), // 1 Gwei - TransactionGasLimit: 50000, - TransactionAmount: big.NewInt(0), + RefillPendingLimit: 200, + RefillFeeCap: big.NewInt(500000000000), // 500 Gwei + RefillTipCap: big.NewInt(1000000000), // 1 Gwei + RefillAmount: big.NewInt(1000000000000000000), // 1 ETH + RefillMinBalance: big.NewInt(500000000000000000), // 0.5 ETH + FeeCap: big.NewInt(100000000000), // 100 Gwei + TipCap: big.NewInt(1000000000), // 1 Gwei + GasLimit: 50000, + Amount: big.NewInt(0), } } diff --git a/pkg/coordinator/tasks/generate_eoa_transactions/task.go b/pkg/coordinator/tasks/generate_eoa_transactions/task.go index 632a6cd..0ac34ed 100644 --- a/pkg/coordinator/tasks/generate_eoa_transactions/task.go +++ b/pkg/coordinator/tasks/generate_eoa_transactions/task.go @@ -120,8 +120,8 @@ func (t *Task) LoadConfig() error { } // parse transaction data - if config.TransactionData != "" { - t.transactionData = common.FromHex(config.TransactionData) + if config.CallData != "" { + t.transactionData = common.FromHex(config.CallData) } t.config = config @@ -257,7 +257,7 @@ func (t *Task) generateTransaction(ctx context.Context, transactionIdx uint64, c toAddr = &addr } - txAmount := new(big.Int).Set(t.config.TransactionAmount) + txAmount := new(big.Int).Set(t.config.Amount) if t.config.RandomAmount { n, err := rand.Int(rand.Reader, txAmount) if err == nil { @@ -272,11 +272,11 @@ func (t *Task) generateTransaction(ctx context.Context, transactionIdx uint64, c var txObj ethtypes.TxData - if t.config.LegacyTransactions { + if t.config.LegacyTxType { txObj = ðtypes.LegacyTx{ Nonce: nonce, - GasPrice: t.config.TransactionFeeCap, - Gas: t.config.TransactionGasLimit, + GasPrice: t.config.FeeCap, + Gas: t.config.GasLimit, To: toAddr, Value: txAmount, Data: txData, @@ -285,9 +285,9 @@ func (t *Task) generateTransaction(ctx context.Context, transactionIdx uint64, c txObj = ðtypes.DynamicFeeTx{ ChainID: t.ctx.Scheduler.GetCoordinator().ClientPool().GetExecutionPool().GetBlockCache().GetChainID(), Nonce: nonce, - GasTipCap: t.config.TransactionTipCap, - GasFeeCap: t.config.TransactionFeeCap, - Gas: t.config.TransactionGasLimit, + GasTipCap: t.config.TipCap, + GasFeeCap: t.config.FeeCap, + Gas: t.config.GasLimit, To: toAddr, Value: txAmount, Data: txData, diff --git a/pkg/coordinator/tasks/tasks.go b/pkg/coordinator/tasks/tasks.go index 5b47732..883364f 100644 --- a/pkg/coordinator/tasks/tasks.go +++ b/pkg/coordinator/tasks/tasks.go @@ -12,6 +12,7 @@ import ( checkconsensussyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_sync_status" checkconsensusvalidatorstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_validator_status" checkexecutionsyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_execution_sync_status" + generateblobtransactions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_blob_transactions" generateblschanges "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_bls_changes" generatedeposits "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_deposits" generateeoatransactions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_eoa_transactions" @@ -36,6 +37,7 @@ var AvailableTaskDescriptors = []*types.TaskDescriptor{ checkconsensussyncstatus.TaskDescriptor, checkconsensusvalidatorstatus.TaskDescriptor, checkexecutionsyncstatus.TaskDescriptor, + generateblobtransactions.TaskDescriptor, generateblschanges.TaskDescriptor, generateeoatransactions.TaskDescriptor, generatedeposits.TaskDescriptor, diff --git a/pkg/coordinator/wallet/blobtx/blob_encode.go b/pkg/coordinator/wallet/blobtx/blob_encode.go new file mode 100644 index 0000000..cb55e84 --- /dev/null +++ b/pkg/coordinator/wallet/blobtx/blob_encode.go @@ -0,0 +1,69 @@ +package blobtx + +import ( + "crypto/sha256" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" +) + +type BlobCommitment struct { + Blob kzg4844.Blob + Commitment kzg4844.Commitment + Proof kzg4844.Proof + VersionedHash common.Hash +} + +func encodeBlobData(data []byte) kzg4844.Blob { + blob := kzg4844.Blob{} + fieldIndex := -1 + + for i := 0; i < len(data); i += 31 { + fieldIndex++ + if fieldIndex == params.BlobTxFieldElementsPerBlob { + break + } + + max := i + 31 + if max > len(data) { + max = len(data) + } + + copy(blob[fieldIndex*32+1:], data[i:max]) + } + + return blob +} + +func EncodeBlob(data []byte) (*BlobCommitment, error) { + dataLen := len(data) + if dataLen > params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1) { + return nil, fmt.Errorf("blob data longer than allowed (length: %v, limit: %v)", dataLen, params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1)) + } + + blobCommitment := BlobCommitment{ + Blob: encodeBlobData(data), + } + + var err error + + // generate blob commitment + blobCommitment.Commitment, err = kzg4844.BlobToCommitment(blobCommitment.Blob) + if err != nil { + return nil, fmt.Errorf("failed generating blob commitment: %w", err) + } + + // generate blob proof + blobCommitment.Proof, err = kzg4844.ComputeBlobProof(blobCommitment.Blob, blobCommitment.Commitment) + if err != nil { + return nil, fmt.Errorf("failed generating blob proof: %w", err) + } + + // build versioned hash + blobCommitment.VersionedHash = sha256.Sum256(blobCommitment.Commitment[:]) + blobCommitment.VersionedHash[0] = params.BlobTxHashVersion + + return &blobCommitment, nil +} diff --git a/pkg/coordinator/wallet/blobtx/blobtx.go b/pkg/coordinator/wallet/blobtx/blobtx.go new file mode 100644 index 0000000..9f63c2a --- /dev/null +++ b/pkg/coordinator/wallet/blobtx/blobtx.go @@ -0,0 +1,150 @@ +package blobtx + +import ( + "crypto/rand" + "fmt" + "io" + mathRand "math/rand" + "net/http" + "os" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" +) + +func GenerateBlobSidecar(blobRefs []string, txIdx, replacementIdx uint64) ([]common.Hash, *types.BlobTxSidecar, error) { + blobHashes := make([]common.Hash, 0) + blobSidecar := &types.BlobTxSidecar{ + Blobs: make([]kzg4844.Blob, 0), + Commitments: make([]kzg4844.Commitment, 0), + Proofs: make([]kzg4844.Proof, 0), + } + + for idx, blobRef := range blobRefs { + blobCommitment, err := parseBlobRefs(blobRef, txIdx, idx, replacementIdx) + if err != nil { + return nil, nil, err + } + + blobHashes = append(blobHashes, blobCommitment.VersionedHash) + blobSidecar.Blobs = append(blobSidecar.Blobs, blobCommitment.Blob) + blobSidecar.Commitments = append(blobSidecar.Commitments, blobCommitment.Commitment) + blobSidecar.Proofs = append(blobSidecar.Proofs, blobCommitment.Proof) + } + + return blobHashes, blobSidecar, nil +} + +func parseBlobRefs(blobRefs string, txIdx uint64, blobIdx int, replacementIdx uint64) (*BlobCommitment, error) { + var err error + + var blobBytes []byte + + for _, blobRef := range strings.Split(blobRefs, ",") { + var blobRefBytes []byte + if strings.HasPrefix(blobRef, "0x") { + blobRefBytes = common.FromHex(blobRef) + } else { + refParts := strings.Split(blobRef, ":") + switch refParts[0] { + case "identifier": + // just some identifier to make assertoor blobs more recognizable + blobLabel := fmt.Sprintf("0x1611BB0000%08dFF%02dFF%04dFEED", txIdx, blobIdx, replacementIdx) + blobRefBytes = common.FromHex(blobLabel) + case "file": + // load blob data from local file + blobRefBytes, err = os.ReadFile(strings.Join(refParts[1:], ":")) + if err != nil { + return nil, err + } + case "url": + // load blob data from remote url + blobRefBytes, err = loadURLRef(strings.Join(refParts[1:], ":")) + if err != nil { + return nil, err + } + case "repeat": + // repeat hex string + if len(refParts) != 3 { + return nil, fmt.Errorf("invalid repeat ref format: %v", blobRef) + } + repeatCount, err2 := strconv.Atoi(refParts[2]) + if err2 != nil { + return nil, fmt.Errorf("invalid repeat count: %v", refParts[2]) + } + repeatBytes := common.FromHex(refParts[1]) + repeatBytesLen := len(repeatBytes) + blobRefBytes = make([]byte, repeatCount*repeatBytesLen) + for i := 0; i < repeatCount; i++ { + copy(blobRefBytes[(i*repeatBytesLen):], repeatBytes) + } + case "random": + // random blob data + var blobLen int + + if len(refParts) > 1 { + var err2 error + + blobLen, err2 = strconv.Atoi(refParts[2]) + if err2 != nil { + return nil, fmt.Errorf("invalid repeat count: %v", refParts[2]) + } + } else { + //nolint:gosec // ignore + blobLen = mathRand.Intn((params.BlobTxFieldElementsPerBlob * (params.BlobTxBytesPerFieldElement - 1)) - len(blobBytes)) + } + blobRefBytes, err = randomBlobData(blobLen) + if err != nil { + return nil, err + } + } + } + + if blobRefBytes == nil { + return nil, fmt.Errorf("unknown blob ref: %v", blobRef) + } + + blobBytes = append(blobBytes, blobRefBytes...) + } + + blobCommitment, err := EncodeBlob(blobBytes) + if err != nil { + return nil, fmt.Errorf("invalid blob: %w", err) + } + + return blobCommitment, nil +} + +func loadURLRef(url string) ([]byte, error) { + //nolint:gosec // ignore + response, err := http.Get(url) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != 200 { + return nil, fmt.Errorf("received http error: %v", response.Status) + } + + return io.ReadAll(response.Body) +} + +func randomBlobData(size int) ([]byte, error) { + data := make([]byte, size) + + n, err := rand.Read(data) + if err != nil { + return nil, err + } + + if n != size { + return nil, fmt.Errorf("could not create random blob data with size %d: %v", size, err) + } + + return data, nil +}