diff --git a/go.mod b/go.mod index 74a0c8ef15f..de25843c048 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/cyberhorsey/webutils v0.0.0-20230314183728-56890c6ddbe7 github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 + github.com/dgraph-io/badger/v4 v4.2.0 github.com/ethereum-optimism/optimism v1.7.4 github.com/ethereum/go-ethereum v1.13.15 github.com/go-git/go-git/v5 v5.12.0 @@ -66,7 +67,6 @@ require ( github.com/btcsuite/btcd v0.24.0 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect - github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cockroachdb/errors v1.11.1 // indirect @@ -87,7 +87,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/docker v25.0.5+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -123,9 +123,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/flatbuffers v1.12.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 // indirect github.com/google/gopacket v1.1.19 // indirect diff --git a/go.sum b/go.sum index 128220d31e7..bb69608f783 100644 --- a/go.sum +++ b/go.sum @@ -70,9 +70,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= -github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -170,8 +167,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -258,8 +253,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018 h1:cNcG4c2n5xanQzp2hMyxDxPYVQmZ91y4WN6fJFlndLo= -github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018/go.mod h1:MIonLggsKgZLUSt414ExgwNtlOL5MuEoAJP514mwGe8= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -463,6 +460,8 @@ github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwr github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -1123,7 +1122,6 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= @@ -1165,8 +1163,6 @@ github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/taikoxyz/optimism v0.0.0-20240624055706-43346f17fbdb h1:t4JcXSwxpUIYq5HrIYCkIfRBc/cytoC6X4YjeJF+qck= -github.com/taikoxyz/optimism v0.0.0-20240624055706-43346f17fbdb/go.mod h1:jKn73pLX8eDIG0Y3XeuUSetepecM8OvRflyPHbi05B4= github.com/taikoxyz/optimism v0.0.0-20240627102435-4845247ff00c h1:Hfhh/icxShwpLdX7RqYzZN1EU40MGWhvSXc2V+ZzTxw= github.com/taikoxyz/optimism v0.0.0-20240627102435-4845247ff00c/go.mod h1:jKn73pLX8eDIG0Y3XeuUSetepecM8OvRflyPHbi05B4= github.com/taikoxyz/taiko-geth v0.0.0-20240504072040-7e1b8b65a3f8 h1:z4juQ4Nyp2T836JTCNC8t3vrbr0K9v2pPUV/ir2dy9s= @@ -1551,6 +1547,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/packages/taiko-client/cmd/flags/preconfapi.go b/packages/taiko-client/cmd/flags/preconfapi.go index 36d71bb5ba0..2b095184570 100644 --- a/packages/taiko-client/cmd/flags/preconfapi.go +++ b/packages/taiko-client/cmd/flags/preconfapi.go @@ -1,6 +1,8 @@ package flags import ( + "time" + "github.com/urfave/cli/v2" ) @@ -13,6 +15,27 @@ var ( Value: 9871, EnvVars: []string{"PRECONFAPI_PORT"}, } + PollingInterval = &cli.DurationFlag{ + Name: "preconfapi.pollingInterval", + Usage: "Interval at which to poll", + Category: preconfAPICategory, + Value: 1 * time.Second, + EnvVars: []string{"POLLING_INTERVAL"}, + } + DBPath = &cli.StringFlag{ + Name: "preconfapi.dbPath", + Usage: "DB Path", + Category: preconfAPICategory, + Value: "/tmp/badgerdb", + EnvVars: []string{"DB_PATH"}, + } + CORSOrigins = &cli.StringSliceFlag{ + Name: "preconfapi.corsORigins", + Usage: "Cors Origins", + Category: preconfAPICategory, + EnvVars: []string{"CORS_ORIGINS"}, + Required: true, + } ) // PreconfAPIFlags contains all preconfirmations API flags @@ -21,4 +44,10 @@ var PreconfAPIFlags = []cli.Flag{ TxGasLimit, PreconfAPIHTTPServerPort, BlobAllowed, + PollingInterval, + L2HTTPEndpoint, + Verbosity, + LogJSON, + DBPath, + CORSOrigins, } diff --git a/packages/taiko-client/preconfapi.sh b/packages/taiko-client/preconfapi.sh new file mode 100755 index 00000000000..0b6d7c0f75d --- /dev/null +++ b/packages/taiko-client/preconfapi.sh @@ -0,0 +1,4 @@ +L2_HTTP=https://rpc.helder.taiko.xyz \ +TAIKO_L1=0xf92d16327ADB54e13D13A90e7Cd092962c9e7a8f \ +CORS_ORIGINS="*" \ +go run cmd/main.go preconfapi \ No newline at end of file diff --git a/packages/taiko-client/preconfapi/config.go b/packages/taiko-client/preconfapi/config.go index 71b76996662..891e502cb9f 100644 --- a/packages/taiko-client/preconfapi/config.go +++ b/packages/taiko-client/preconfapi/config.go @@ -1,6 +1,8 @@ package preconfapi import ( + "time" + "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" @@ -12,6 +14,10 @@ type Config struct { BlobAllowed bool HTTPPort uint64 ProposeBlockTxGasLimit uint64 + PollingInterval time.Duration + L2HTTPEndpoint string + DBPath string + CORSOrigins []string } // NewConfigFromCliContext initializes a Config instance from @@ -22,5 +28,9 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { BlobAllowed: c.Bool(flags.BlobAllowed.Name), HTTPPort: c.Uint64(flags.PreconfAPIHTTPServerPort.Name), ProposeBlockTxGasLimit: c.Uint64(flags.TxGasLimit.Name), + PollingInterval: c.Duration(flags.PollingInterval.Name), + L2HTTPEndpoint: c.String(flags.L2HTTPEndpoint.Name), + DBPath: c.String(flags.DBPath.Name), + CORSOrigins: c.StringSlice(flags.CORSOrigins.Name), }, nil } diff --git a/packages/taiko-client/preconfapi/model/transaction.go b/packages/taiko-client/preconfapi/model/transaction.go new file mode 100644 index 00000000000..c4a32521554 --- /dev/null +++ b/packages/taiko-client/preconfapi/model/transaction.go @@ -0,0 +1,118 @@ +package model + +type Transaction struct { + To *AddressParam `json:"to"` + CreatedContract *AddressParam `json:"created_contract"` + Hash string `json:"hash"` + Result string `json:"result"` + Confirmations int `json:"confirmations"` + Status *string `json:"status"` + Block *int `json:"block"` + Timestamp *string `json:"timestamp"` + ConfirmationDuration []int `json:"confirmation_duration"` + From AddressParam `json:"from"` + Value string `json:"value"` + Fee Fee `json:"fee"` + GasPrice string `json:"gas_price"` + Type *int `json:"type"` + GasUsed *uint64 `json:"gas_used"` + GasLimit uint64 `json:"gas_limit"` + MaxFeePerGas *string `json:"max_fee_per_gas"` + MaxPriorityFeePerGas *string `json:"max_priority_fee_per_gas"` + PriorityFee *string `json:"priority_fee"` + BaseFeePerGas *string `json:"base_fee_per_gas"` + TxBurntFee *string `json:"tx_burnt_fee"` + Nonce int `json:"nonce"` + Position *uint `json:"position"` + RevertReason *TransactionRevertReason `json:"revert_reason"` + RawInput string `json:"raw_input"` + DecodedInput *DecodedInput `json:"decoded_input"` + TokenTransfers *[]TokenTransfer `json:"token_transfers"` + TokenTransfersOverflow bool `json:"token_transfers_overflow"` + ExchangeRate string `json:"exchange_rate"` + Method *string `json:"method"` + TxTypes []TransactionType `json:"tx_types"` + TxTag *string `json:"tx_tag"` + Actions []TxAction `json:"actions"` + L1Fee *string `json:"l1_fee"` + L1FeeScalar *string `json:"l1_fee_scalar"` + L1GasPrice *string `json:"l1_gas_price"` + L1GasUsed *string `json:"l1_gas_used"` + HasErrorInInternalTxs *bool `json:"has_error_in_internal_txs"` +} + +type TransactionRevertReason struct { + Raw string `json:"raw"` +} + +type Fee struct { + Type string `json:"type"` + Value string `json:"value"` +} + +type TransactionType string + +const ( + TxTypeTokenTransfer TransactionType = "token_transfer" + TxTypeContractCreation TransactionType = "contract_creation" + TxTypeContractCall TransactionType = "contract_call" + TxTypeTokenCreation TransactionType = "token_creation" + TxTypeCoinTransfer TransactionType = "coin_transfer" +) + +type AddressParam struct { + UserTags + Hash string `json:"hash"` + ImplementationName *string `json:"implementation_name"` + Name *string `json:"name"` + IsContract bool `json:"is_contract"` + IsVerified *bool `json:"is_verified"` +} + +type DecodedInput struct { + // Define fields as needed +} + +type TxAction struct { + // Define fields as needed +} + +type UserTags struct { + // Define fields as needed +} + +type TokenTransfer struct { + Token TokenInfo `json:"token"` + Total TokenTotal `json:"total"` + Type string `json:"type"` + TxHash string `json:"tx_hash"` + From AddressParam `json:"from"` + To AddressParam `json:"to"` + Timestamp string `json:"timestamp"` + BlockHash string `json:"block_hash"` + LogIndex string `json:"log_index"` + Method *string `json:"method,omitempty"` +} + +type TokenInfo struct { + Type string `json:"type"` + // Add other fields as necessary +} + +type TokenTotal struct { + Erc20TotalPayload *Erc20TotalPayload `json:"erc20_total_payload,omitempty"` + Erc721TotalPayload *Erc721TotalPayload `json:"erc721_total_payload,omitempty"` + Erc1155TotalPayload *Erc1155TotalPayload `json:"erc1155_total_payload,omitempty"` +} + +type Erc20TotalPayload struct { + // Define fields as necessary +} + +type Erc721TotalPayload struct { + // Define fields as necessary +} + +type Erc1155TotalPayload struct { + // Define fields as necessary +} diff --git a/packages/taiko-client/preconfapi/preconfapi.go b/packages/taiko-client/preconfapi/preconfapi.go index 7edafa5c304..a233896d889 100644 --- a/packages/taiko-client/preconfapi/preconfapi.go +++ b/packages/taiko-client/preconfapi/preconfapi.go @@ -2,20 +2,36 @@ package preconfapi import ( "context" + "encoding/json" "errors" "fmt" + "math/big" "net/http" + "strconv" + "sync" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" "github.com/taikoxyz/taiko-mono/packages/taiko-client/preconfapi/builder" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/preconfapi/model" "github.com/taikoxyz/taiko-mono/packages/taiko-client/preconfapi/server" + + badger "github.com/dgraph-io/badger/v4" ) type PreconfAPI struct { - cfg *Config - server *server.PreconfAPIServer + cfg *Config + server *server.PreconfAPIServer + wg *sync.WaitGroup + ctx context.Context + ethclient *ethclient.Client + latestSeenBlockNumber uint64 + chainID *big.Int + db *badger.DB } // InitFromCli New initializes the given proposer instance based on the command line flags. @@ -28,7 +44,7 @@ func (p *PreconfAPI) InitFromCli(ctx context.Context, c *cli.Context) error { return p.InitFromConfig(ctx, cfg) } -func (p *PreconfAPI) InitFromConfig(_ context.Context, cfg *Config) (err error) { +func (p *PreconfAPI) InitFromConfig(ctx context.Context, cfg *Config) (err error) { txBuilders := make(map[string]builder.TxBuilder) txBuilders["blob"] = builder.NewBlobTransactionBuilder( cfg.TaikoL1Address, @@ -40,23 +56,52 @@ func (p *PreconfAPI) InitFromConfig(_ context.Context, cfg *Config) (err error) cfg.ProposeBlockTxGasLimit, ) + p.cfg = cfg + p.wg = &sync.WaitGroup{} + p.ctx = ctx + + p.ethclient, err = ethclient.DialContext(ctx, cfg.L2HTTPEndpoint) + if err != nil { + return err + } + + // get latest block number from L2 + p.latestSeenBlockNumber, err = p.ethclient.BlockNumber(ctx) + if err != nil { + return err + } + + p.chainID, err = p.ethclient.ChainID(ctx) + if err != nil { + return err + } + + p.db, err = badger.Open(badger.DefaultOptions(p.cfg.DBPath)) + if err != nil { + return err + } + if p.server, err = server.New(&server.NewPreconfAPIServerOpts{ - TxBuilders: txBuilders, + TxBuilders: txBuilders, + DB: p.db, + CORSOrigins: p.cfg.CORSOrigins, }); err != nil { return err } - p.cfg = cfg - return nil } func (p *PreconfAPI) Start() error { + p.wg.Add(1) + go p.pollLoop(p.ctx) + go func() { if err := p.server.Start(fmt.Sprintf(":%v", p.cfg.HTTPPort)); !errors.Is(err, http.ErrServerClosed) { log.Crit("Failed to start http server", "error", err) } }() + return nil } @@ -65,8 +110,195 @@ func (p *PreconfAPI) Close(ctx context.Context) { if err := p.server.Shutdown(ctx); err != nil { log.Error("Failed to shut down prover server", "error", err) } + + p.db.Close() + + p.wg.Wait() } func (p *PreconfAPI) Name() string { return "preconfapi" } + +func (p *PreconfAPI) pollLoop(ctx context.Context) { + defer p.wg.Done() + + t := time.NewTicker(p.cfg.PollingInterval) + + for { + select { + case <-ctx.Done(): + return + case <-t.C: + if err := p.poll(); err != nil { + log.Error("Failed to poll", "error", err) + } + } + } +} + +func (p *PreconfAPI) poll() error { + // get latest block number from L2 + latestBlockNumber, err := p.ethclient.BlockNumber(context.Background()) + if err != nil { + return err + } + + if latestBlockNumber < p.latestSeenBlockNumber { + return nil + } + + for i := p.latestSeenBlockNumber; i <= latestBlockNumber; i++ { + var result json.RawMessage + err := p.ethclient.Client().CallContext( + context.Background(), + &result, + "eth_getBlockByNumber", + fmt.Sprintf("0x%v", new(big.Int).SetUint64(latestBlockNumber).Text(16)), + true, + ) + if err != nil { + return err + } + + var preconfBlock CustomBlock + err = json.Unmarshal(result, &preconfBlock) + if err != nil { + return err + } + + for _, tx := range preconfBlock.Transactions { + if err := p.db.Update(func(txn *badger.Txn) error { + _, err := txn.Get(common.HexToHash(tx.Hash).Bytes()) + if err == nil { + return nil + } + + receipt, err := p.ethclient.TransactionReceipt(p.ctx, common.Hash(common.HexToHash(tx.Hash).Bytes())) + if err != nil { + return err + } + + fromAddress := model.AddressParam{ + Hash: tx.From, + IsContract: false, + } + + toAddress := &model.AddressParam{ + Hash: "", + IsContract: false, + } + if tx.To != nil { + to := tx.To + toAddress.Hash = *to + code, err := p.ethclient.CodeAt(p.ctx, common.HexToAddress(*to), nil) + if err != nil { + return err + } + + toAddress.IsContract = len(code) > 2 + } + + status := "ok" + if receipt.Status != 1 { + status = "error" + } + + maxFeePerGas := tx.MaxFeePerGas + + maxPrioFee := tx.MaxPriorityFeePerGas + + ts := time.Now().UTC().Format("2006-01-02T15:04:05.000000Z") + + txT, err := strconv.ParseInt(tx.Type, 0, 64) + if err != nil { + return err + } + + txType := int(txT) + + nonceT, err := strconv.ParseInt(tx.Nonce, 0, 64) + if err != nil { + return err + } + + nonce := int(nonceT) + + txTypes := []model.TransactionType{} + if toAddress.IsContract { + txTypes = append(txTypes, model.TxTypeContractCall) + } else { + txTypes = append(txTypes, model.TxTypeCoinTransfer) + } + + if tx.To == nil { + txTypes = append(txTypes, model.TxTypeContractCreation) + } + + baseFee := "1" // TODO + + gpT, err := strconv.ParseInt(tx.GasPrice, 0, 64) + if err != nil { + return err + } + + gas, err := strconv.ParseInt(tx.Gas, 0, 64) + if err != nil { + return err + } + + modelTx := model.Transaction{ + Actions: make([]model.TxAction, 0), + From: fromAddress, + To: toAddress, + Fee: model.Fee{ + Type: "actual", + Value: new(big.Int).Mul(new(big.Int).SetInt64(gpT), new(big.Int).SetUint64(receipt.GasUsed)).String(), + }, + Hash: tx.Hash, + Value: tx.Value, + GasPrice: tx.GasPrice, + Nonce: nonce, + Block: new(int), + GasLimit: uint64(gas), + TxTypes: txTypes, + Status: &status, + Confirmations: int(latestBlockNumber) - int(receipt.BlockNumber.Uint64()), + GasUsed: &receipt.GasUsed, + MaxFeePerGas: &maxFeePerGas, + MaxPriorityFeePerGas: &maxPrioFee, + BaseFeePerGas: &baseFee, + Position: &receipt.TransactionIndex, + Timestamp: &ts, + ConfirmationDuration: []int{}, + RawInput: tx.Input, + Type: &txType, + // Add other fields as necessary + } + + *modelTx.Block = int(latestBlockNumber) + + marshalled, err := json.Marshal(modelTx) + if err != nil { + return err + } + + err = txn.Set(common.HexToHash(tx.Hash).Bytes(), marshalled) + if err != nil { + log.Error("Failed to set transaction in BadgerDB", "error", err) + return err + } + + log.Info("saved tx", "hash", common.HexToHash(tx.Hash)) + + return nil + }); err != nil { + return err + } + } + } + + p.latestSeenBlockNumber = latestBlockNumber + + return nil +} diff --git a/packages/taiko-client/preconfapi/server/api.go b/packages/taiko-client/preconfapi/server/api.go index fb5b3613b09..e3d9a381ef0 100644 --- a/packages/taiko-client/preconfapi/server/api.go +++ b/packages/taiko-client/preconfapi/server/api.go @@ -3,12 +3,16 @@ package server import ( "bytes" "encoding/hex" + "encoding/json" "net/http" + badger "github.com/dgraph-io/badger/v4" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/labstack/echo/v4" "github.com/taikoxyz/taiko-mono/packages/taiko-client/preconfapi/builder" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/preconfapi/model" ) // @title Taiko Preconf Server API @@ -129,6 +133,40 @@ func (s *PreconfAPIServer) BuildBlocks(c echo.Context) error { return c.JSON(http.StatusOK, buildBlockResponse{RLPEncodedTx: hexEncodedTx}) } +func (s *PreconfAPIServer) GetTransactionByHash(c echo.Context) error { + hash := c.Param("hash") + + // get from badger db + tx := &model.Transaction{} + + if err := s.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(common.HexToHash(hash).Bytes()) + if err != nil { + return err + } + + if item == nil { + return nil + } + + if err := item.Value(func(val []byte) error { + return json.Unmarshal(val, tx) + }); err != nil { + return err + } + + return nil + }); err != nil { + return c.JSON(http.StatusNotFound, nil) + } + + if tx == nil { + return c.JSON(http.StatusNotFound, nil) + } + + return c.JSON(http.StatusOK, tx) +} + func paramsToOpts(params []buildBlockParams) builder.BuildBlocksUnsignedOpts { opts := make([]builder.BuildBlockUnsignedOpts, 0) diff --git a/packages/taiko-client/preconfapi/server/server.go b/packages/taiko-client/preconfapi/server/server.go index e5b75c28822..0d8ef9d89e2 100644 --- a/packages/taiko-client/preconfapi/server/server.go +++ b/packages/taiko-client/preconfapi/server/server.go @@ -5,6 +5,7 @@ import ( "net/http" "os" + badger "github.com/dgraph-io/badger/v4" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -23,13 +24,16 @@ import ( // @license.url https://github.com/taikoxyz/taiko-mono/blob/main/LICENSE.md // PreconfAPIServer represents a proposer server instance. type PreconfAPIServer struct { + db *badger.DB echo *echo.Echo txBuilders map[string]builder.TxBuilder // calldata or blob map to txbuilder type } // NewPreconfAPIServerOpts contains all configurations for creating a prover server instance. type NewPreconfAPIServerOpts struct { - TxBuilders map[string]builder.TxBuilder + TxBuilders map[string]builder.TxBuilder + DB *badger.DB + CORSOrigins []string } // New creates a new prover server instance. @@ -37,10 +41,11 @@ func New(opts *NewPreconfAPIServerOpts) (*PreconfAPIServer, error) { srv := &PreconfAPIServer{ echo: echo.New(), txBuilders: opts.TxBuilders, + db: opts.DB, } srv.echo.HideBanner = true - srv.configureMiddleware() + srv.configureMiddleware(opts.CORSOrigins) srv.configureRoutes() return srv, nil @@ -72,7 +77,7 @@ func LogSkipper(c echo.Context) bool { } // configureMiddleware configures the server middlewares. -func (s *PreconfAPIServer) configureMiddleware() { +func (s *PreconfAPIServer) configureMiddleware(corsOrigins []string) { s.echo.Use(middleware.RequestID()) s.echo.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ @@ -83,6 +88,12 @@ func (s *PreconfAPIServer) configureMiddleware() { `"bytes_in":${bytes_in},"bytes_out":${bytes_out}}}` + "\n", Output: os.Stdout, })) + + // Add CORS middleware + s.echo.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: corsOrigins, + AllowCredentials: true, + })) } // configureRoutes contains all routes which will be used by prover server. @@ -91,4 +102,5 @@ func (s *PreconfAPIServer) configureRoutes() { s.echo.GET("/healthz", s.Health) s.echo.POST("/blocks/build", s.BuildBlocks) s.echo.POST("/block/build", s.BuildBlock) + s.echo.GET("/tx/:hash", s.GetTransactionByHash) } diff --git a/packages/taiko-client/preconfapi/server/server_test.go b/packages/taiko-client/preconfapi/server/server_test.go index e7e14c00c14..c08aa9f8261 100644 --- a/packages/taiko-client/preconfapi/server/server_test.go +++ b/packages/taiko-client/preconfapi/server/server_test.go @@ -26,7 +26,7 @@ func (s *PreconfAPIServerTestSuite) SetupTest() { s.Nil(err) p.echo.HideBanner = true - p.configureMiddleware() + p.configureMiddleware([]string{"*"}) p.configureRoutes() s.s = p s.testServer = httptest.NewServer(p.echo) diff --git a/packages/taiko-client/preconfapi/types.go b/packages/taiko-client/preconfapi/types.go new file mode 100644 index 00000000000..43b825e2889 --- /dev/null +++ b/packages/taiko-client/preconfapi/types.go @@ -0,0 +1,50 @@ +package preconfapi + +type CustomTransaction struct { + BlockHash string `json:"blockHash"` + BlockNumber string `json:"blockNumber"` + From string `json:"from"` + Gas string `json:"gas"` + GasPrice string `json:"gasPrice"` + MaxFeePerGas string `json:"maxFeePerGas"` + MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"` + Hash string `json:"hash"` + Input string `json:"input"` + Nonce string `json:"nonce"` + To *string `json:"to"` + TransactionIndex string `json:"transactionIndex"` + Value string `json:"value"` + Type string `json:"type"` + AccessList []interface{} `json:"accessList"` + ChainID string `json:"chainId"` + V string `json:"v"` + R string `json:"r"` + S string `json:"s"` + YParity string `json:"yParity"` +} + +type CustomBlock struct { + BaseFeePerGas string `json:"baseFeePerGas"` + Difficulty string `json:"difficulty"` + ExtraData string `json:"extraData"` + GasLimit string `json:"gasLimit"` + GasUsed string `json:"gasUsed"` + Hash string `json:"hash"` + LogsBloom string `json:"logsBloom"` + Miner string `json:"miner"` + MixHash string `json:"mixHash"` + Nonce string `json:"nonce"` + Number string `json:"number"` + ParentHash string `json:"parentHash"` + ReceiptsRoot string `json:"receiptsRoot"` + Sha3Uncles string `json:"sha3Uncles"` + Size string `json:"size"` + StateRoot string `json:"stateRoot"` + Timestamp string `json:"timestamp"` + TotalDifficulty string `json:"totalDifficulty"` + Transactions []CustomTransaction `json:"transactions"` + TransactionsRoot string `json:"transactionsRoot"` + Uncles []interface{} `json:"uncles"` + Withdrawals []interface{} `json:"withdrawals"` + WithdrawalsRoot string `json:"withdrawalsRoot"` +}