diff --git a/README.md b/README.md index 39d5fcd21f..52bbe0a1a7 100644 --- a/README.md +++ b/README.md @@ -136,16 +136,16 @@ you'd expect. HTTP based JSON-RPC API options: - * `--rpc` Enable the HTTP-RPC server - * `--rpcaddr` HTTP-RPC server listening interface (default: `localhost`) - * `--rpcport` HTTP-RPC server listening port (default: `8545`) - * `--rpcapi` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) - * `--rpccorsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) + * `--http` Enable the HTTP-RPC server + * `--http.addr` HTTP-RPC server listening interface (default: `localhost`) + * `--http.port` HTTP-RPC server listening port (default: `8545`) + * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) + * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) * `--ws` Enable the WS-RPC server - * `--wsaddr` WS-RPC server listening interface (default: `localhost`) - * `--wsport` WS-RPC server listening port (default: `8546`) - * `--wsapi` API's offered over the WS-RPC interface (default: `eth,net,web3`) - * `--wsorigins` Origins from which to accept websockets requests + * `--ws.addr` WS-RPC server listening interface (default: `localhost`) + * `--ws.port` WS-RPC server listening port (default: `8546`) + * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) + * `--ws.origins` Origins from which to accept websockets requests * `--ipcdisable` Disable the IPC-RPC server * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,shh,txpool,web3`) * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) diff --git a/accounts/abi/error.go b/accounts/abi/error.go index 8d61d574c5..b63a215ad9 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -39,11 +39,11 @@ func formatSliceString(kind reflect.Kind, sliceSize int) string { // type in t. func sliceTypeCheck(t Type, val reflect.Value) error { if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { - return typeErr(formatSliceString(t.getType().Kind(), t.Size), val.Type()) + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) } if t.T == ArrayTy && val.Len() != t.Size { - return typeErr(formatSliceString(t.Elem.getType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) } if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { @@ -52,8 +52,8 @@ func sliceTypeCheck(t Type, val reflect.Value) error { } } - if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.getType().Kind() { - return typeErr(formatSliceString(t.Elem.getType().Kind(), t.Size), val.Type()) + if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) } return nil } @@ -66,10 +66,10 @@ func typeCheck(t Type, value reflect.Value) error { } // Check base type validity. Element types will be checked later on. - if t.getType().Kind() != value.Kind() { - return typeErr(t.getType().Kind(), value.Kind()) + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) } else if t.T == FixedBytesTy && t.Size != value.Len() { - return typeErr(t.getType(), value.Type()) + return typeErr(t.GetType(), value.Type()) } else { return nil } diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 8d73b49fed..4769bd984f 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -176,7 +176,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty overloadedNames[fieldName] = fieldName fields = append(fields, reflect.StructField{ Name: fieldName, // reflect.StructOf will panic for any exported field. - Type: cType.getType(), + Type: cType.GetType(), Tag: reflect.StructTag("json:\"" + c.Name + "\""), }) elems = append(elems, &cType) @@ -214,7 +214,8 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty return } -func (t Type) getType() reflect.Type { +// GetType returns the reflection type of the ABI type. +func (t Type) GetType() reflect.Type { switch t.T { case IntTy: return reflectIntType(false, t.Size) @@ -225,9 +226,9 @@ func (t Type) getType() reflect.Type { case StringTy: return reflect.TypeOf("") case SliceTy: - return reflect.SliceOf(t.Elem.getType()) + return reflect.SliceOf(t.Elem.GetType()) case ArrayTy: - return reflect.ArrayOf(t.Size, t.Elem.getType()) + return reflect.ArrayOf(t.Size, t.Elem.GetType()) case TupleTy: return t.TupleType case AddressTy: diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 2f90742139..0348ad24d7 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -110,7 +110,7 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) { return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") } // convert - array := reflect.New(t.getType()).Elem() + array := reflect.New(t.GetType()).Elem() reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) return array.Interface(), nil @@ -131,10 +131,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) if t.T == SliceTy { // declare our slice - refSlice = reflect.MakeSlice(t.getType(), size, size) + refSlice = reflect.MakeSlice(t.GetType(), size, size) } else if t.T == ArrayTy { // declare our array - refSlice = reflect.New(t.getType()).Elem() + refSlice = reflect.New(t.GetType()).Elem() } else { return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") } @@ -158,7 +158,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) } func forTupleUnpack(t Type, output []byte) (interface{}, error) { - retval := reflect.New(t.getType()).Elem() + retval := reflect.New(t.GetType()).Elem() virtualArgs := 0 for index, elem := range t.TupleElems { marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 0ebb073009..949333183f 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -29,7 +29,6 @@ import ( "github.com/celo-org/celo-blockchain/core/types" blscrypto "github.com/celo-org/celo-blockchain/crypto/bls" "github.com/celo-org/celo-blockchain/event" - "github.com/celo-org/celo-blockchain/internal/ethapi" "github.com/celo-org/celo-blockchain/log" "github.com/celo-org/celo-blockchain/rpc" "github.com/celo-org/celo-blockchain/signer/core" @@ -196,8 +195,13 @@ func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]by return signature, nil } +// signTransactionResult represents the signinig result returned by clef. +type signTransactionResult struct { + Raw hexutil.Bytes `json:"raw"` + Tx *types.Transaction `json:"tx"` +} + func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - res := ethapi.SignTransactionResult{} data := hexutil.Bytes(tx.Data()) var to *common.MixedcaseAddress if tx.To() != nil { @@ -213,6 +217,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio To: to, From: common.NewMixedcaseAddress(account.Address), } + var res signTransactionResult if err := api.client.Call(&res, "account_signTransaction", args); err != nil { return nil, err } diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index b325854564..47a065fd9a 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -54,7 +54,7 @@ var ( // ErrAccountAlreadyExists is returned if an account attempted to import is // already present in the keystore. - ErrAccountAlreadyExists = errors.New("account alreaady exists") + ErrAccountAlreadyExists = errors.New("account already exists") ) // KeyStoreType is the reflect type of a keystore backend. diff --git a/build/ci.go b/build/ci.go index d8b2257f76..54d603b9ad 100644 --- a/build/ci.go +++ b/build/ci.go @@ -1215,6 +1215,8 @@ func doPurge(cmdline []string) { if err != nil { log.Fatal(err) } + fmt.Printf("Found %d blobs\n", len(blobs)) + // Iterate over the blobs, collect and sort all unstable builds for i := 0; i < len(blobs); i++ { if !strings.Contains(blobs[i].Name, "unstable") { @@ -1236,6 +1238,7 @@ func doPurge(cmdline []string) { break } } + fmt.Printf("Deleting %d blobs\n", len(blobs)) // Delete all marked as such and return if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { log.Fatal(err) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 5cd3da8ab1..029d67c8fd 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -114,9 +114,9 @@ Some snags and todos Clef listens to HTTP requests on `rpcaddr`:`rpcport` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). -Some of these call can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a users decides to ignore the confirmation request. +Some of these calls can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a user decides to ignore the confirmation request. -The External API is **untrusted**: it does not accept credentials over this API, nor does it expect that requests have any authority. +The External API is **untrusted**: it does not accept credentials, nor does it expect that requests have any authority. ### Internal UI API @@ -145,13 +145,11 @@ See the [external API changelog](extapi_changelog.md) for information about chan All hex encoded values must be prefixed with `0x`. -## Methods - ### account_new #### Create new password protected account -The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory. +The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and store it in the keystore directory. The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. #### Arguments @@ -160,7 +158,6 @@ None #### Result - address [string]: account address that is derived from the generated key - - url [string]: location of the keyfile #### Sample call ```json @@ -172,14 +169,11 @@ None } ``` Response -``` +```json { "id": 0, "jsonrpc": "2.0", - "result": { - "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", - "url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" - } + "result": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" } ``` @@ -195,8 +189,6 @@ None #### Result - array with account records: - account.address [string]: account address that is derived from the generated key - - account.type [string]: type of the - - account.url [string]: location of the account #### Sample call ```json @@ -207,21 +199,13 @@ None } ``` Response -``` +```json { "id": 1, "jsonrpc": "2.0", "result": [ - { - "address": "0xafb2f771f58513609765698f65d3f2f0224a956f", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f" - }, - { - "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" - } + "0xafb2f771f58513609765698f65d3f2f0224a956f", + "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" ] } ``` @@ -229,10 +213,10 @@ Response ### account_signTransaction #### Sign transactions - Signs a transactions and responds with the signed transaction in RLP encoded form. + Signs a transaction and responds with the signed transaction in RLP-encoded and JSON forms. #### Arguments - 2. transaction object: + 1. transaction object: - `from` [address]: account to send the transaction from - `to` [address]: receiver account. If omitted or `0x`, will cause contract creation. - `gas` [number]: maximum amount of gas to burn @@ -240,12 +224,13 @@ Response - `value` [number:optional]: amount of Wei to send with the transaction - `data` [data:optional]: input data - `nonce` [number]: account nonce - 3. method signature [string:optional] + 1. method signature [string:optional] - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. #### Result - - signed transaction in RLP encoded form [data] + - raw [data]: signed transaction in RLP encoded form + - tx [json]: signed transaction in JSON form #### Sample call ```json @@ -270,11 +255,22 @@ Response ```json { - "id": 2, "jsonrpc": "2.0", - "error": { - "code": -32000, - "message": "Request denied" + "id": 2, + "result": { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1234", + "gas": "0x55555", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x1234", + "input": "0xabcd", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } } } ``` @@ -326,7 +322,7 @@ Response Bash example: ```bash -#curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +> curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} ``` @@ -372,7 +368,7 @@ Response ### account_signTypedData #### Sign data - Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature. + Signs a chunk of structured data conformant to [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and returns the calculated signature. #### Arguments - account [address]: account to sign with @@ -468,7 +464,7 @@ Response ### account_ecRecover -#### Sign data +#### Recover the signing address Derive the address from the account that was used to sign data with content type `text/plain` and the signature. @@ -486,7 +482,6 @@ Derive the address from the account that was used to sign data with content type "jsonrpc": "2.0", "method": "account_ecRecover", "params": [ - "data/plain", "0xaabbccdd", "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" ] @@ -502,117 +497,36 @@ Response } ``` -### account_import +### account_version -#### Import account - Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore - format. - -#### Arguments - - account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export) +#### Get external API version -#### Result - - imported key [object]: - - key.address [address]: address of the imported key - - key.type [string]: type of the account - - key.url [string]: key URL - -#### Sample call -```json -{ - "id": 6, - "jsonrpc": "2.0", - "method": "account_import", - "params": [ - { - "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "401c39a7c7af0388491c3d3ecb39f532" - }, - "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" - }, - "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" - }, - "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", - "version": 3 - } - ] -} -``` -Response - -```json -{ - "id": 6, - "jsonrpc": "2.0", - "result": { - "address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5" - } -} -``` - -### account_export - -#### Export account from keystore - Export a private key from the keystore. The exported private key is encrypted with the original password. When the - key is imported later this password is required. +Get the version of the external API used by Clef. #### Arguments - - account [address]: export private key that is associated with this account + +None #### Result - - exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for - more information + +* external API version [string] #### Sample call ```json { - "id": 5, + "id": 0, "jsonrpc": "2.0", - "method": "account_export", - "params": [ - "0xc7412fc59930fd90099c917a50e5f11d0934b2f5" - ] + "method": "account_version", + "params": [] } ``` -Response +Response ```json { - "id": 5, - "jsonrpc": "2.0", - "result": { - "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "401c39a7c7af0388491c3d3ecb39f532" - }, - "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" - }, - "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" - }, - "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", - "version": 3 - } + "id": 0, + "jsonrpc": "2.0", + "result": "6.0.0" } ``` @@ -624,7 +538,7 @@ By starting the signer with the switch `--stdio-ui-test`, the signer will invoke denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. -All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key. +All methods in this API use object-based parameters, so that there can be no mixup of parameters: each piece of data is accessed by key. See the [ui API changelog](intapi_changelog.md) for information about changes to this API. @@ -783,12 +697,10 @@ Invoked when a request for account listing has been made. { "accounts": [ { - "type": "Account", "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", "address": "0x123409812340981234098123409812deadbeef42" }, { - "type": "Account", "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", "address": "0xcafebabedeadbeef34098123409812deadbeef42" } @@ -818,7 +730,13 @@ Invoked when a request for account listing has been made. { "address": "0x123409812340981234098123409812deadbeef42", "raw_data": "0x01020304", - "message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "messages": [ + { + "name": "message", + "value": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "type": "text/plain" + } + ], "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", "meta": { "remote": "signer binary", @@ -828,12 +746,34 @@ Invoked when a request for account listing has been made. } ] } +``` + +### ApproveNewAccount / `ui_approveNewAccount` + +Invoked when a request for creating a new account has been made. +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ui_approveNewAccount", + "params": [ + { + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} ``` ### ShowInfo / `ui_showInfo` -The UI should show the info to the user. Does not expect response. +The UI should show the info (a single message) to the user. Does not expect response. #### Sample call @@ -843,9 +783,7 @@ The UI should show the info to the user. Does not expect response. "id": 9, "method": "ui_showInfo", "params": [ - { - "text": "Tests completed" - } + "Tests completed" ] } @@ -853,18 +791,16 @@ The UI should show the info to the user. Does not expect response. ### ShowError / `ui_showError` -The UI should show the info to the user. Does not expect response. +The UI should show the error (a single message) to the user. Does not expect response. ```json { "jsonrpc": "2.0", "id": 2, - "method": "ShowError", + "method": "ui_showError", "params": [ - { - "text": "Testing 'ShowError'" - } + "Something bad happened!" ] } @@ -878,9 +814,36 @@ When implementing rate-limited rules, this callback should be used. TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onApprovedTx", + "params": [ + { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1", + "gas": "0x333", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } + ] +} +``` + ### OnSignerStartup / `ui_onSignerStartup` -This method provide the UI with information about what API version the signer uses (both internal and external) as well as build-info and external API, +This method provides the UI with information about what API version the signer uses (both internal and external) as well as build-info and external API, in k/v-form. Example call: @@ -904,6 +867,27 @@ Example call: ``` +### OnInputRequired / `ui_onInputRequired` + +Invoked when Clef requires user input (e.g. a password). + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onInputRequired", + "params": [ + { + "title": "Account password", + "prompt": "Please enter the password for account 0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "isPassword": true + } + ] +} +``` + ### Rules for UI apis diff --git a/cmd/clef/main.go b/cmd/clef/main.go index ff5b5ca8a6..66e9877daf 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -40,7 +40,6 @@ import ( "github.com/celo-org/celo-blockchain/cmd/utils" "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/common/hexutil" - "github.com/celo-org/celo-blockchain/console/prompt" "github.com/celo-org/celo-blockchain/core/types" "github.com/celo-org/celo-blockchain/crypto" "github.com/celo-org/celo-blockchain/internal/ethapi" @@ -289,7 +288,7 @@ func initializeSecrets(c *cli.Context) error { text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!" var password string for { - password = getPassPhrase(text, true) + password = utils.GetPassPhrase(text, true) if err := core.ValidatePasswordFormat(password); err != nil { fmt.Printf("invalid password: %v\n", err) } else { @@ -362,7 +361,7 @@ func setCredential(ctx *cli.Context) error { utils.Fatalf("Invalid address specified: %s", addr) } address := common.HexToAddress(addr) - password := getPassPhrase("Please enter a password to store for this address:", true) + password := utils.GetPassPhrase("Please enter a password to store for this address:", true) fmt.Println() stretchedKey, err := readMasterKey(ctx, nil) @@ -719,7 +718,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { } password = resp.Text } else { - password = getPassPhrase("Decrypt master seed of clef", false) + password = utils.GetPassPhrase("Decrypt master seed of clef", false) } masterSeed, err := decryptSeed(cipherKey, password) if err != nil { @@ -888,27 +887,6 @@ func testExternalUI(api *core.SignerAPI) { } -// getPassPhrase retrieves the password associated with clef, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -// TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one. -func getPassPhrase(query string, confirmation bool) string { - fmt.Println(query) - password, err := prompt.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - if confirmation { - confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passwords do not match") - } - } - return password -} - type encryptedSeedStorage struct { Description string `json:"description"` Version int `json:"version"` diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 14f8328237..96951a198a 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -19,12 +19,15 @@ package main import ( "fmt" "net" + "os" "strings" "time" + "github.com/celo-org/celo-blockchain/cmd/devp2p/internal/v4test" "github.com/celo-org/celo-blockchain/cmd/utils" "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/internal/utesting" "github.com/celo-org/celo-blockchain/node" "github.com/celo-org/celo-blockchain/p2p/discover" "github.com/celo-org/celo-blockchain/p2p/enode" @@ -43,6 +46,7 @@ var ( discv4ResolveCommand, discv4ResolveJSONCommand, discv4CrawlCommand, + discv4TestCommand, }, } discv4PingCommand = cli.Command{ @@ -77,6 +81,12 @@ var ( Action: discv4Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv4TestCommand = cli.Command{ + Name: "test", + Usage: "Runs tests against a node", + Action: discv4Test, + Flags: []cli.Flag{remoteEnodeFlag, testPatternFlag, testListen1Flag, testListen2Flag}, + } ) var ( @@ -101,6 +111,25 @@ var ( Usage: "Time limit for the crawl.", Value: 30 * time.Minute, } + remoteEnodeFlag = cli.StringFlag{ + Name: "remote", + Usage: "Enode of the remote node under test", + EnvVar: "REMOTE_ENODE", + } + testPatternFlag = cli.StringFlag{ + Name: "run", + Usage: "Pattern of test suite(s) to run", + } + testListen1Flag = cli.StringFlag{ + Name: "listen1", + Usage: "IP address of the first tester", + Value: v4test.Listen1, + } + testListen2Flag = cli.StringFlag{ + Name: "listen2", + Usage: "IP address of the second tester", + Value: v4test.Listen2, + } ) var networkIdFlag = cli.Uint64Flag{ @@ -193,6 +222,28 @@ func discv4Crawl(ctx *cli.Context) error { return nil } +func discv4Test(ctx *cli.Context) error { + // Configure test package globals. + if !ctx.IsSet(remoteEnodeFlag.Name) { + return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) + } + v4test.Remote = ctx.String(remoteEnodeFlag.Name) + v4test.Listen1 = ctx.String(testListen1Flag.Name) + v4test.Listen2 = ctx.String(testListen2Flag.Name) + + // Filter and run test cases. + tests := v4test.AllTests + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + results := utesting.RunTests(tests, os.Stdout) + if fails := utesting.CountFailures(results); fails > 0 { + return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) + } + fmt.Printf("%v/%v passed\n", len(tests), len(tests)) + return nil +} + // startV4 starts an ephemeral discovery V4 node. func startV4(ctx *cli.Context) *discover.UDPv4 { ln, config := makeDiscoveryConfig(ctx) diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go new file mode 100644 index 0000000000..eee4c56fd1 --- /dev/null +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -0,0 +1,467 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v4test + +import ( + "bytes" + "crypto/rand" + "fmt" + "net" + "reflect" + "time" + + "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/internal/utesting" + "github.com/celo-org/celo-blockchain/p2p/discover/v4wire" +) + +const ( + expiration = 20 * time.Second + wrongPacket = 66 + macSize = 256 / 8 +) + +var ( + // Remote node under test + Remote string + // IP where the first tester is listening, port will be assigned + Listen1 string = "127.0.0.1" + // IP where the second tester is listening, port will be assigned + // Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least) + Listen2 string = "127.0.0.2" +) + +type pingWithJunk struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 + JunkData1 uint + JunkData2 []byte +} + +func (req *pingWithJunk) Name() string { return "PING/v4" } +func (req *pingWithJunk) Kind() byte { return v4wire.PingPacket } + +type pingWrongType struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 +} + +func (req *pingWrongType) Name() string { return "WRONG/v4" } +func (req *pingWrongType) Kind() byte { return wrongPacket } + +func futureExpiration() uint64 { + return uint64(time.Now().Add(expiration).Unix()) +} + +// This test just sends a PING packet and expects a response. +func BasicPing(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// checkPong verifies that reply is a valid PONG matching the given ping hash. +func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { + if reply == nil || reply.Kind() != v4wire.PongPacket { + return fmt.Errorf("expected PONG reply, got %v", reply) + } + pong := reply.(*v4wire.Pong) + if !bytes.Equal(pong.ReplyTok, pingHash) { + return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) + } + wantEndpoint := te.localEndpoint(te.l1) + if !reflect.DeepEqual(pong.To, wantEndpoint) { + return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, wantEndpoint) + } + if v4wire.Expired(pong.Expiration) { + return fmt.Errorf("PONG is expired (%v)", pong.Expiration) + } + return nil +} + +// This test sends a PING packet with wrong 'to' field and expects a PONG response. +func PingWrongTo(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: wrongEndpoint, + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with wrong 'from' field and expects a PONG response. +func PingWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data at the end and expects a PONG +// response. The remote node should respond because EIP-8 mandates ignoring additional +// trailing data. +func PingExtraData(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &pingWithJunk{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data and wrong 'from' field +// and expects a PONG response. +func PingExtraDataWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + req := pingWithJunk{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + pingHash := te.send(te.l1, &req) + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with an expiration in the past. +// The remote node should not respond. +func PingPastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: -futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test sends an invalid packet. The remote node should not respond. +func WrongPacketType(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &pingWrongType{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test verifies that the default behaviour of ignoring 'from' fields is unaffected by +// the bonding process. After bonding, it pings the target with a different from endpoint. +func BondThenPingWithWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test just sends FINDNODE. The remote node should not reply +// because the endpoint proof has not completed. +func FindnodeWithoutEndpointProof(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + req := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(req.Target[:]) + te.send(te.l1, &req) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no response, got", reply) + } +} + +// BasicFindnode sends a FINDNODE request after performing the endpoint +// proof. The remote node should respond. +func BasicFindnode(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } +} + +// This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends +// FINDNODE to read the remote table. The remote node should not return the node contained +// in the unsolicited NEIGHBORS packet. +func UnsolicitedNeighbors(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + // Send unsolicited NEIGHBORS response. + fakeKey, _ := crypto.GenerateKey() + encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey) + neighbors := v4wire.Neighbors{ + Expiration: futureExpiration(), + Nodes: []v4wire.Node{{ + ID: encFakeKey, + IP: net.IP{1, 2, 3, 4}, + UDP: 30303, + TCP: 30303, + }}, + } + te.send(te.l1, &neighbors) + + // Check if the remote node included the fake node. + te.send(te.l1, &v4wire.Findnode{ + Expiration: futureExpiration(), + Target: encFakeKey, + }) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } + nodes := reply.(*v4wire.Neighbors).Nodes + if contains(nodes, encFakeKey) { + t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") + } +} + +// This test sends FINDNODE with an expiration timestamp in the past. +// The remote node should not respond. +func FindnodePastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: -futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + for { + reply, _, _ := te.read(te.l1) + if reply == nil { + return + } else if reply.Kind() == v4wire.NeighborsPacket { + t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request") + } + } +} + +// bond performs the endpoint proof with the remote node. +func bond(t *utesting.T, te *testenv) { + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, hash, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: hash, + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + // TODO: maybe verify pong data here + gotPong = true + } + } +} + +// This test attempts to perform a traffic amplification attack against a +// 'victim' endpoint using FINDNODE. In this attack scenario, the attacker +// attempts to complete the endpoint proof non-interactively by sending a PONG +// with mismatching reply token from the 'victim' endpoint. The attack works if +// the remote node does not verify the PONG reply token field correctly. The +// attacker could then perform traffic amplification by sending many FINDNODE +// requests to the discovery node, which would reply to the 'victim' address. +func FindnodeAmplificationInvalidPongHash(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Send PING to start endpoint verification. + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, _, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + // Send PONG from this node ID, but with invalid ReplyTok. + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: make([]byte, macSize), + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + gotPong = true + } + } + + // Now send FINDNODE. The remote node should not respond because our + // PONG did not reference the PING hash. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l1) + if reply != nil && reply.Kind() == v4wire.NeighborsPacket { + t.Error("Got neighbors") + } +} + +// This test attempts to perform a traffic amplification attack using FINDNODE. +// The attack works if the remote node does not verify the IP address of FINDNODE +// against the endpoint verification proof done by PING/PONG. +func FindnodeAmplificationWrongIP(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Do the endpoint proof from the l1 IP. + bond(t, te) + + // Now send FINDNODE from the same node ID, but different IP address. + // The remote node should not respond. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l2, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l2) + if reply != nil { + t.Error("Got NEIGHORS response for FINDNODE from wrong IP") + } +} + +var AllTests = []utesting.Test{ + {Name: "Ping/Basic", Fn: BasicPing}, + {Name: "Ping/WrongTo", Fn: PingWrongTo}, + {Name: "Ping/WrongFrom", Fn: PingWrongFrom}, + {Name: "Ping/ExtraData", Fn: PingExtraData}, + {Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom}, + {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, + {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, + {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, + {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, + {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, + {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, + {Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration}, + {Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash}, + {Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP}, +} diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go new file mode 100644 index 0000000000..020b8a9bb2 --- /dev/null +++ b/cmd/devp2p/internal/v4test/framework.go @@ -0,0 +1,123 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v4test + +import ( + "crypto/ecdsa" + "fmt" + "net" + "time" + + "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/p2p/discover/v4wire" + "github.com/celo-org/celo-blockchain/p2p/enode" +) + +const waitTime = 300 * time.Millisecond + +type testenv struct { + l1, l2 net.PacketConn + key *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr +} + +func newTestEnv(remote string, listen1, listen2 string) *testenv { + l1, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen1)) + if err != nil { + panic(err) + } + l2, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen2)) + if err != nil { + panic(err) + } + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + node, err := enode.Parse(enode.ValidSchemes, remote) + if err != nil { + panic(err) + } + if node.IP() == nil || node.UDP() == 0 { + var ip net.IP + var tcpPort, udpPort int + if ip = node.IP(); ip == nil { + ip = net.ParseIP("127.0.0.1") + } + if tcpPort = node.TCP(); tcpPort == 0 { + tcpPort = 30303 + } + if udpPort = node.TCP(); udpPort == 0 { + udpPort = 30303 + } + node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort) + } + addr := &net.UDPAddr{IP: node.IP(), Port: node.UDP()} + return &testenv{l1, l2, key, node, addr} +} + +func (te *testenv) close() { + te.l1.Close() + te.l2.Close() +} + +func (te *testenv) send(c net.PacketConn, req v4wire.Packet) []byte { + packet, hash, err := v4wire.Encode(te.key, req) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", req.Name(), err)) + } + if _, err := c.WriteTo(packet, te.remoteAddr); err != nil { + panic(fmt.Errorf("can't send %v: %v", req.Name(), err)) + } + return hash +} + +func (te *testenv) read(c net.PacketConn) (v4wire.Packet, []byte, error) { + buf := make([]byte, 2048) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return nil, nil, err + } + n, _, err := c.ReadFrom(buf) + if err != nil { + return nil, nil, err + } + p, _, hash, err := v4wire.Decode(buf[:n]) + return p, hash, err +} + +func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint { + addr := c.LocalAddr().(*net.UDPAddr) + return v4wire.Endpoint{ + IP: addr.IP.To4(), + UDP: uint16(addr.Port), + TCP: 0, + } +} + +func (te *testenv) remoteEndpoint() v4wire.Endpoint { + return v4wire.NewEndpoint(te.remoteAddr, 0) +} + +func contains(ns []v4wire.Node, key v4wire.Pubkey) bool { + for _, n := range ns { + if n.ID == key { + return true + } + } + return false +} diff --git a/cmd/devp2p/keycmd.go b/cmd/devp2p/keycmd.go new file mode 100644 index 0000000000..edc2c6b4a3 --- /dev/null +++ b/cmd/devp2p/keycmd.go @@ -0,0 +1,105 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "net" + + "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/p2p/enode" + "gopkg.in/urfave/cli.v1" +) + +var ( + keyCommand = cli.Command{ + Name: "key", + Usage: "Operations on node keys", + Subcommands: []cli.Command{ + keyGenerateCommand, + keyToNodeCommand, + }, + } + keyGenerateCommand = cli.Command{ + Name: "generate", + Usage: "Generates node key files", + ArgsUsage: "keyfile", + Action: genkey, + } + keyToNodeCommand = cli.Command{ + Name: "to-enode", + Usage: "Creates an enode URL from a node key file", + ArgsUsage: "keyfile", + Action: keyToURL, + Flags: []cli.Flag{hostFlag, tcpPortFlag, udpPortFlag}, + } +) + +var ( + hostFlag = cli.StringFlag{ + Name: "ip", + Usage: "IP address of the node", + Value: "127.0.0.1", + } + tcpPortFlag = cli.IntFlag{ + Name: "tcp", + Usage: "TCP port of the node", + Value: 30303, + } + udpPortFlag = cli.IntFlag{ + Name: "udp", + Usage: "UDP port of the node", + Value: 30303, + } +) + +func genkey(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need key file as argument") + } + file := ctx.Args().Get(0) + + key, err := crypto.GenerateKey() + if err != nil { + return fmt.Errorf("could not generate key: %v", err) + } + return crypto.SaveECDSA(file, key) +} + +func keyToURL(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need key file as argument") + } + + var ( + file = ctx.Args().Get(0) + host = ctx.String(hostFlag.Name) + tcp = ctx.Int(tcpPortFlag.Name) + udp = ctx.Int(udpPortFlag.Name) + ) + key, err := crypto.LoadECDSA(file) + if err != nil { + return err + } + ip := net.ParseIP(host) + if ip == nil { + return fmt.Errorf("invalid IP address %q", host) + } + node := enode.NewV4(&key.PublicKey, ip, tcp, udp) + fmt.Println(node.URLv4()) + return nil +} diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index a603847e01..f56b179297 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -58,6 +58,7 @@ func init() { // Add subcommands. app.Commands = []cli.Command{ enrdumpCommand, + keyCommand, discv4Command, discv5Command, dnsCommand, diff --git a/cmd/ethkey/changepassword.go b/cmd/ethkey/changepassword.go index 732d90916c..a25ca3fdea 100644 --- a/cmd/ethkey/changepassword.go +++ b/cmd/ethkey/changepassword.go @@ -51,7 +51,7 @@ Change the password of a keyfile.`, } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) @@ -67,7 +67,7 @@ Change the password of a keyfile.`, } newPhrase = strings.TrimRight(string(content), "\r\n") } else { - newPhrase = promptPassphrase(true) + newPhrase = utils.GetPassPhrase("", true) } // Encrypt the key with the new passphrase. diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go index 0634b0c0f7..0eeeaaaa47 100644 --- a/cmd/ethkey/generate.go +++ b/cmd/ethkey/generate.go @@ -94,7 +94,7 @@ If you want to encrypt an existing private key, it can be specified by setting } // Encrypt key with passphrase. - passphrase := promptPassphrase(true) + passphrase := getPassphrase(ctx, true) scryptN, scryptP := keystore.StandardScryptN, keystore.StandardScryptP if ctx.Bool("lightkdf") { scryptN, scryptP = keystore.LightScryptN, keystore.LightScryptP diff --git a/cmd/ethkey/inspect.go b/cmd/ethkey/inspect.go index b8c9ea15ed..d31752d9a6 100644 --- a/cmd/ethkey/inspect.go +++ b/cmd/ethkey/inspect.go @@ -60,7 +60,7 @@ make sure to use this feature with great caution!`, } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) diff --git a/cmd/ethkey/message.go b/cmd/ethkey/message.go index 47b8684550..bdaa8a8ad0 100644 --- a/cmd/ethkey/message.go +++ b/cmd/ethkey/message.go @@ -62,7 +62,7 @@ To sign a message contained in a file, use the --msgfile flag. } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) diff --git a/cmd/ethkey/utils.go b/cmd/ethkey/utils.go index b31260c7ef..b5903b7f5f 100644 --- a/cmd/ethkey/utils.go +++ b/cmd/ethkey/utils.go @@ -23,36 +23,14 @@ import ( "strings" "github.com/celo-org/celo-blockchain/cmd/utils" - "github.com/celo-org/celo-blockchain/console/prompt" "github.com/celo-org/celo-blockchain/crypto" "gopkg.in/urfave/cli.v1" ) -// promptPassphrase prompts the user for a passphrase. Set confirmation to true -// to require the user to confirm the passphrase. -func promptPassphrase(confirmation bool) string { - passphrase, err := prompt.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - - if confirmation { - confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if passphrase != confirm { - utils.Fatalf("Passwords do not match") - } - } - - return passphrase -} - // getPassphrase obtains a passphrase given by the user. It first checks the // --passfile command line flag and ultimately prompts the user for a // passphrase. -func getPassphrase(ctx *cli.Context) string { +func getPassphrase(ctx *cli.Context, confirmation bool) string { // Look for the --passwordfile flag. passphraseFile := ctx.String(passphraseFlag.Name) if passphraseFile != "" { @@ -65,7 +43,7 @@ func getPassphrase(ctx *cli.Context) string { } // Otherwise prompt the user for the passphrase. - return promptPassphrase(false) + return utils.GetPassPhrase("", confirmation) } // signHash is a helper function that calculates a hash for the given message diff --git a/cmd/evm/README.md b/cmd/evm/README.md new file mode 100644 index 0000000000..418417475d --- /dev/null +++ b/cmd/evm/README.md @@ -0,0 +1,268 @@ +## EVM state transition tool + +The `evm t8n` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +## Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a `geth`-based implementation and a `parityvm`-based +implementation. + +### Command line params + +Command line params that has to be supported are +``` + + --trace Output full trace logs to files .jsonl + --trace.nomemory Disable full memory dump in traces + --trace.nostack Disable stack output in traces + --output.alloc alloc Determines where to put the alloc of the post-state. + `stdout` - into the stdout output + `stderr` - into the stderr output + --output.result result Determines where to put the result (stateroot, txroot etc) of the post-state. + `stdout` - into the stdout output + `stderr` - into the stderr output + --state.fork value Name of ruleset to use. + --state.chainid value ChainID to use (default: 1) + --state.reward value Mining reward. Set to -1 to disable (default: 0) + +``` + +### Error codes and output + +All logging should happen against the `stderr`. +There are a few (not many) errors that can occur, those are defined below. + +#### EVM-based errors (`2` to `9`) + +- Other EVM error. Exit code `2` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. +- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` + is invoked targeting a block which history has not been provided for, the program will + exit with code `4`. + +#### IO errors (`10`-`20`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code `10` +- IO problems: failure to load or save files, the program will exit with code `11` + +## Examples +### Basic usage + +Invoking it with the provided example files +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json +``` +Two resulting files: + +`alloc.json`: +```json +{ + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } +} +``` +`result.json`: +```json +{ + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + 1 + ] +} +``` + +We can make them spit out the data to e.g. `stdout` like this: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout +``` +Output: +```json +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } + }, + "result": { + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + 1 + ] + } +} +``` + +## About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. +- For each ommer (mined by `0xbb`), with blocknumber `N-delta` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` + +To make `state_t8n` apply these, the following inputs are required: + +- `state.reward` + - For ethash, it is `5000000000000000000` `wei`, + - If this is not defined, mining rewards are not applied, + - A value of `0` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an `address` and a `delta`. This + is done via the `env`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +`./testdata/5/env.json`: +```json +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} +``` +When applying this, using a reward of `0x08` +Output: +```json +{ + "alloc": { + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "balance": "0x88" + }, + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { + "balance": "0x70" + }, + "0xcccccccccccccccccccccccccccccccccccccccc": { + "balance": "0x60" + } + } +} +``` +### Future EIPS + +It is also possible to experiment with future eips that are not yet defined in a hard fork. +Example, putting EIP-1344 into Frontier: +``` +./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json +``` + +### Block history + +The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. +If a required blockhash is not provided, the exit code should be `4`: +Example where blockhashes are provided: +``` +./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace +``` +``` +cat trace-0.jsonl | grep BLOCKHASH -C2 +``` +``` +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"depth":1,"refund":0,"opName":"PUSH1","error":""} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"depth":1,"refund":0,"opName":"BLOCKHASH","error":""} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"depth":1,"refund":0,"opName":"STOP","error":""} +{"output":"","gasUsed":"0x17","time":155861} +``` + +In this example, the caller has not provided the required blockhash: +``` +./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace +``` +``` +ERROR(4): getHash(3) invoked, blockhash for that block not provided +``` +Error code: 4 +### Chaining + +Another thing that can be done, is to chain invocations: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json +INFO [06-29|11:52:04.934] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" +INFO [06-29|11:52:04.936] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" +INFO [06-29|11:52:04.936] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" + +``` +What happened here, is that we first applied two identical transactions, so the second one was rejected. +Then, taking the poststate alloc as the input for the next state, we tried again to include +the same two transactions: this time, both failed due to too low nonce. + +In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the +actual blocknumber (exposed to the EVM) would not increase. + diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go new file mode 100644 index 0000000000..6ca65b11d6 --- /dev/null +++ b/cmd/evm/internal/t8ntool/execution.go @@ -0,0 +1,253 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package t8ntool + +import ( + "fmt" + "math/big" + "os" + + "github.com/celo-org/celo-blockchain/common" + "github.com/celo-org/celo-blockchain/common/math" + "github.com/celo-org/celo-blockchain/consensus/misc" + "github.com/celo-org/celo-blockchain/core" + "github.com/celo-org/celo-blockchain/core/rawdb" + "github.com/celo-org/celo-blockchain/core/state" + "github.com/celo-org/celo-blockchain/core/types" + "github.com/celo-org/celo-blockchain/core/vm" + "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/ethdb" + "github.com/celo-org/celo-blockchain/log" + "github.com/celo-org/celo-blockchain/params" + "github.com/celo-org/celo-blockchain/rlp" + "golang.org/x/crypto/sha3" +) + +type Prestate struct { + Env stEnv `json:"env"` + Pre core.GenesisAlloc `json:"pre"` +} + +// ExecutionResult contains the execution status after running a state test, any +// error that might have occurred and a dump of the final state if requested. +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom" gencodec:"required"` + Receipts types.Receipts `json:"receipts"` + Rejected []int `json:"rejected,omitempty"` +} + +type ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} + +//go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go +type stEnv struct { + Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` + Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"` + GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` + Number uint64 `json:"currentNumber" gencodec:"required"` + Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` +} + +type stEnvMarshaling struct { + Coinbase common.UnprefixedAddress + Difficulty *math.HexOrDecimal256 + GasLimit math.HexOrDecimal64 + Number math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 +} + +// Apply applies a set of transactions to a pre-state +func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, + txs types.Transactions, miningReward int64, + getTracerFn func(txIndex int) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) { + + // Capture errors for BLOCKHASH operation, if we haven't been supplied the + // required blockhashes + var hashError error + getHash := func(num uint64) common.Hash { + if pre.Env.BlockHashes == nil { + hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) + return common.Hash{} + } + h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] + if !ok { + hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) + } + return h + } + var ( + statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) + signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) + gaspool = new(core.GasPool) + blockHash = common.Hash{0x13, 0x37} + rejectedTxs []int + includedTxs types.Transactions + gasUsed = uint64(0) + receipts = make(types.Receipts, 0) + txIndex = 0 + ) + gaspool.AddGas(pre.Env.GasLimit) + vmContext := vm.Context{ + // CanTransfer: core.CanTransfer, + // Transfer: core.Transfer, + Coinbase: pre.Env.Coinbase, + BlockNumber: new(big.Int).SetUint64(pre.Env.Number), + Time: new(big.Int).SetUint64(pre.Env.Timestamp), + GetHash: getHash, + // GasPrice and Origin needs to be set per transaction + } + // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's + // done in StateProcessor.Process(block, ...), right before transactions are applied. + if chainConfig.DAOForkSupport && + chainConfig.DAOForkBlock != nil && + chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { + misc.ApplyDAOHardFork(statedb) + } + + for i, tx := range txs { + msg, err := tx.AsMessage(signer) + if err != nil { + log.Info("rejected tx", "index", i, "hash", tx.Hash(), "error", err) + rejectedTxs = append(rejectedTxs, i) + continue + } + tracer, err := getTracerFn(txIndex) + if err != nil { + return nil, nil, err + } + vmConfig.Tracer = tracer + vmConfig.Debug = (tracer != nil) + statedb.Prepare(tx.Hash(), blockHash, txIndex) + vmContext.GasPrice = msg.GasPrice() + vmContext.Origin = msg.From() + + evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) + snapshot := statedb.Snapshot() + // (ret []byte, usedGas uint64, failed bool, err error) + msgResult, err := core.ApplyMessage(evm, msg, gaspool) + if err != nil { + statedb.RevertToSnapshot(snapshot) + log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err) + rejectedTxs = append(rejectedTxs, i) + continue + } + includedTxs = append(includedTxs, tx) + if hashError != nil { + return nil, nil, NewError(ErrorMissingBlockhash, hashError) + } + gasUsed += msgResult.UsedGas + // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx + { + var root []byte + if chainConfig.IsByzantium(vmContext.BlockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() + } + + receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed) + receipt.TxHash = tx.Hash() + receipt.GasUsed = msgResult.UsedGas + // if the transaction created a contract, store the creation address in the receipt. + if msg.To() == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce()) + } + // Set the receipt logs and create a bloom for filtering + receipt.Logs = statedb.GetLogs(tx.Hash()) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + // These three are non-consensus fields + //receipt.BlockHash + //receipt.BlockNumber = + receipt.TransactionIndex = uint(txIndex) + receipts = append(receipts, receipt) + } + txIndex++ + } + statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) + // Add mining reward? + if miningReward > 0 { + // Add mining reward. The mining reward may be `0`, which only makes a difference in the cases + // where + // - the coinbase suicided, or + // - there are only 'bad' transactions, which aren't executed. In those cases, + // the coinbase gets no txfee, so isn't created, and thus needs to be touched + var ( + blockReward = big.NewInt(miningReward) + minerReward = new(big.Int).Set(blockReward) + perOmmer = new(big.Int).Div(blockReward, big.NewInt(32)) + ) + for _, ommer := range pre.Env.Ommers { + // Add 1/32th for each ommer included + minerReward.Add(minerReward, perOmmer) + // Add (8-delta)/8 + reward := big.NewInt(8) + reward.Sub(reward, big.NewInt(0).SetUint64(ommer.Delta)) + reward.Mul(reward, blockReward) + reward.Div(reward, big.NewInt(8)) + statedb.AddBalance(ommer.Address, reward) + } + statedb.AddBalance(pre.Env.Coinbase, minerReward) + } + // Commit block + root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) + return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) + } + execRs := &ExecutionResult{ + StateRoot: root, + TxRoot: types.DeriveSha(includedTxs), + ReceiptRoot: types.DeriveSha(receipts), + Bloom: types.CreateBloom(receipts), + LogsHash: rlpHash(statedb.Logs()), + Receipts: receipts, + Rejected: rejectedTxs, + } + return statedb, execRs, nil +} + +func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { + sdb := state.NewDatabase(db) + statedb, _ := state.New(common.Hash{}, sdb, nil) + for addr, a := range accounts { + statedb.SetCode(addr, a.Code) + statedb.SetNonce(addr, a.Nonce) + statedb.SetBalance(addr, a.Balance) + for k, v := range a.Storage { + statedb.SetState(addr, k, v) + } + } + // Commit and re-open to start with a clean state. + root, _ := statedb.Commit(false) + statedb, _ = state.New(root, sdb, nil) + return statedb +} + +func rlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, x) + hw.Sum(h[:0]) + return h +} diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go new file mode 100644 index 0000000000..008780593f --- /dev/null +++ b/cmd/evm/internal/t8ntool/flags.go @@ -0,0 +1,99 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package t8ntool + +import ( + "fmt" + "strings" + + "github.com/celo-org/celo-blockchain/core/vm" + "github.com/celo-org/celo-blockchain/tests" + "gopkg.in/urfave/cli.v1" +) + +var ( + TraceFlag = cli.BoolFlag{ + Name: "trace", + Usage: "Output full trace logs to files .jsonl", + } + TraceDisableMemoryFlag = cli.BoolFlag{ + Name: "trace.nomemory", + Usage: "Disable full memory dump in traces", + } + TraceDisableStackFlag = cli.BoolFlag{ + Name: "trace.nostack", + Usage: "Disable stack output in traces", + } + OutputAllocFlag = cli.StringFlag{ + Name: "output.alloc", + Usage: "Determines where to put the `alloc` of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "alloc.json", + } + OutputResultFlag = cli.StringFlag{ + Name: "output.result", + Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "result.json", + } + InputAllocFlag = cli.StringFlag{ + Name: "input.alloc", + Usage: "`stdin` or file name of where to find the prestate alloc to use.", + Value: "alloc.json", + } + InputEnvFlag = cli.StringFlag{ + Name: "input.env", + Usage: "`stdin` or file name of where to find the prestate env to use.", + Value: "env.json", + } + InputTxsFlag = cli.StringFlag{ + Name: "input.txs", + Usage: "`stdin` or file name of where to find the transactions to apply.", + Value: "txs.json", + } + RewardFlag = cli.Int64Flag{ + Name: "state.reward", + Usage: "Mining reward. Set to -1 to disable", + Value: 0, + } + ChainIDFlag = cli.Int64Flag{ + Name: "state.chainid", + Usage: "ChainID to use", + Value: 1, + } + ForknameFlag = cli.StringFlag{ + Name: "state.fork", + Usage: fmt.Sprintf("Name of ruleset to use."+ + "\n\tAvailable forknames:"+ + "\n\t %v"+ + "\n\tAvailable extra eips:"+ + "\n\t %v"+ + "\n\tSyntax (+ExtraEip)", + strings.Join(tests.AvailableForks(), "\n\t "), + strings.Join(vm.ActivateableEips(), ", ")), + Value: "Istanbul", + } + VerbosityFlag = cli.IntFlag{ + Name: "verbosity", + Usage: "sets the verbosity level", + Value: 3, + } +) diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go new file mode 100644 index 0000000000..c3dcd7e6a6 --- /dev/null +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -0,0 +1,80 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package t8ntool + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/celo-org/celo-blockchain/common" + "github.com/celo-org/celo-blockchain/common/math" +) + +var _ = (*stEnvMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s stEnv) MarshalJSON() ([]byte, error) { + type stEnv struct { + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + } + var enc stEnv + enc.Coinbase = common.UnprefixedAddress(s.Coinbase) + enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) + enc.GasLimit = math.HexOrDecimal64(s.GasLimit) + enc.Number = math.HexOrDecimal64(s.Number) + enc.Timestamp = math.HexOrDecimal64(s.Timestamp) + enc.BlockHashes = s.BlockHashes + enc.Ommers = s.Ommers + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stEnv) UnmarshalJSON(input []byte) error { + type stEnv struct { + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + } + var dec stEnv + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Coinbase == nil { + return errors.New("missing required field 'currentCoinbase' for stEnv") + } + s.Coinbase = common.Address(*dec.Coinbase) + if dec.Difficulty == nil { + return errors.New("missing required field 'currentDifficulty' for stEnv") + } + s.Difficulty = (*big.Int)(dec.Difficulty) + if dec.GasLimit == nil { + return errors.New("missing required field 'currentGasLimit' for stEnv") + } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { + return errors.New("missing required field 'currentNumber' for stEnv") + } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { + return errors.New("missing required field 'currentTimestamp' for stEnv") + } + s.Timestamp = uint64(*dec.Timestamp) + if dec.BlockHashes != nil { + s.BlockHashes = dec.BlockHashes + } + if dec.Ommers != nil { + s.Ommers = dec.Ommers + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go new file mode 100644 index 0000000000..2b2f3e2057 --- /dev/null +++ b/cmd/evm/internal/t8ntool/transition.go @@ -0,0 +1,276 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package t8ntool + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + + "github.com/celo-org/celo-blockchain/common" + "github.com/celo-org/celo-blockchain/core" + "github.com/celo-org/celo-blockchain/core/state" + "github.com/celo-org/celo-blockchain/core/types" + "github.com/celo-org/celo-blockchain/core/vm" + "github.com/celo-org/celo-blockchain/log" + "github.com/celo-org/celo-blockchain/params" + "github.com/celo-org/celo-blockchain/tests" + "gopkg.in/urfave/cli.v1" +) + +const ( + ErrorEVM = 2 + ErrorVMConfig = 3 + ErrorMissingBlockhash = 4 + + ErrorJson = 10 + ErrorIO = 11 + + stdinSelector = "stdin" +) + +type NumberedError struct { + errorCode int + err error +} + +func NewError(errorCode int, err error) *NumberedError { + return &NumberedError{errorCode, err} +} + +func (n *NumberedError) Error() string { + return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error()) +} + +func (n *NumberedError) Code() int { + return n.errorCode +} + +type input struct { + Alloc core.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + Txs types.Transactions `json:"txs,omitempty"` +} + +func Main(ctx *cli.Context) error { + // Configure the go-ethereum logger + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) + log.Root().SetHandler(glogger) + + var ( + err error + tracer vm.Tracer + ) + var getTracer func(txIndex int) (vm.Tracer, error) + + if ctx.Bool(TraceFlag.Name) { + // Configure the EVM logger + logConfig := &vm.LogConfig{ + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), + Debug: true, + } + var prevFile *os.File + // This one closes the last file + defer func() { + if prevFile != nil { + prevFile.Close() + } + }() + getTracer = func(txIndex int) (vm.Tracer, error) { + if prevFile != nil { + prevFile.Close() + } + traceFile, err := os.Create(fmt.Sprintf("trace-%d.jsonl", txIndex)) + if err != nil { + return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) + } + prevFile = traceFile + return vm.NewJSONLogger(logConfig, traceFile), nil + } + } else { + getTracer = func(txIndex int) (tracer vm.Tracer, err error) { + return nil, nil + } + } + // We need to load three things: alloc, env and transactions. May be either in + // stdin input or in files. + // Check if anything needs to be read from stdin + var ( + prestate Prestate + txs types.Transactions // txs to apply + allocStr = ctx.String(InputAllocFlag.Name) + + envStr = ctx.String(InputEnvFlag.Name) + txStr = ctx.String(InputTxsFlag.Name) + inputData = &input{} + ) + + if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + decoder.Decode(inputData) + } + if allocStr != stdinSelector { + inFile, err := os.Open(allocStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(&inputData.Alloc); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err)) + } + } + + if envStr != stdinSelector { + inFile, err := os.Open(envStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading env file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + var env stEnv + if err := decoder.Decode(&env); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling env-file: %v", err)) + } + inputData.Env = &env + } + + if txStr != stdinSelector { + inFile, err := os.Open(txStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + var txs types.Transactions + if err := decoder.Decode(&txs); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) + } + inputData.Txs = txs + } + + prestate.Pre = inputData.Alloc + prestate.Env = *inputData.Env + txs = inputData.Txs + + // Iterate over all the tests, run them and aggregate the results + vmConfig := vm.Config{ + Tracer: tracer, + Debug: (tracer != nil), + } + // Construct the chainconfig + var chainConfig *params.ChainConfig + if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { + return NewError(ErrorVMConfig, fmt.Errorf("Failed constructing chain configuration: %v", err)) + } else { + chainConfig = cConf + vmConfig.ExtraEips = extraEips + } + // Set the chain id + chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + + // Run the test and aggregate the result + state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) + if err != nil { + return err + } + // Dump the excution result + //postAlloc := state.DumpGenesisFormat(false, false, false) + collector := make(Alloc) + state.DumpToCollector(collector, false, false, false, nil, -1) + return dispatchOutput(ctx, result, collector) + +} + +type Alloc map[common.Address]core.GenesisAccount + +func (g Alloc) OnRoot(common.Hash) {} + +func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { + balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10) + var storage map[common.Hash]common.Hash + if dumpAccount.Storage != nil { + storage = make(map[common.Hash]common.Hash) + for k, v := range dumpAccount.Storage { + storage[k] = common.HexToHash(v) + } + } + genesisAccount := core.GenesisAccount{ + Code: common.FromHex(dumpAccount.Code), + Storage: storage, + Balance: balance, + Nonce: dumpAccount.Nonce, + } + g[addr] = genesisAccount +} + +// saveFile marshalls the object to the given file +func saveFile(filename string, data interface{}) error { + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + if err = ioutil.WriteFile(filename, b, 0644); err != nil { + return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) + } + return nil +} + +// dispatchOutput writes the output data to either stderr or stdout, or to the specified +// files +func dispatchOutput(ctx *cli.Context, result *ExecutionResult, alloc Alloc) error { + stdOutObject := make(map[string]interface{}) + stdErrObject := make(map[string]interface{}) + dispatch := func(fName, name string, obj interface{}) error { + switch fName { + case "stdout": + stdOutObject[name] = obj + case "stderr": + stdErrObject[name] = obj + default: // save to file + if err := saveFile(fName, obj); err != nil { + return err + } + } + return nil + } + if err := dispatch(ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { + return err + } + if err := dispatch(ctx.String(OutputResultFlag.Name), "result", result); err != nil { + return err + } + if len(stdOutObject) > 0 { + b, err := json.MarshalIndent(stdOutObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stdout.Write(b) + } + if len(stdErrObject) > 0 { + b, err := json.MarshalIndent(stdErrObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stderr.Write(b) + } + return nil +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 36d2560800..942e07c7bd 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -22,6 +22,7 @@ import ( "math/big" "os" + "github.com/celo-org/celo-blockchain/cmd/evm/internal/t8ntool" "github.com/celo-org/celo-blockchain/cmd/utils" "gopkg.in/urfave/cli.v1" ) @@ -126,6 +127,27 @@ var ( } ) +var stateTransitionCommand = cli.Command{ + Name: "transition", + Aliases: []string{"t8n"}, + Usage: "executes a full state transition", + Action: t8ntool.Main, + Flags: []cli.Flag{ + t8ntool.TraceFlag, + t8ntool.TraceDisableMemoryFlag, + t8ntool.TraceDisableStackFlag, + t8ntool.OutputAllocFlag, + t8ntool.OutputResultFlag, + t8ntool.InputAllocFlag, + t8ntool.InputEnvFlag, + t8ntool.InputTxsFlag, + t8ntool.ForknameFlag, + t8ntool.ChainIDFlag, + t8ntool.RewardFlag, + t8ntool.VerbosityFlag, + }, +} + func init() { app.Flags = []cli.Flag{ BenchFlag, @@ -156,13 +178,18 @@ func init() { disasmCommand, runCommand, stateTestCommand, + stateTransitionCommand, } cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate } func main() { if err := app.Run(os.Args); err != nil { + code := 1 + if ec, ok := err.(*t8ntool.NumberedError); ok { + code = ec.Code() + } fmt.Fprintln(os.Stderr, err) - os.Exit(1) + os.Exit(code) } } diff --git a/cmd/evm/poststate.json b/cmd/evm/poststate.json new file mode 100644 index 0000000000..9ee17f18d1 --- /dev/null +++ b/cmd/evm/poststate.json @@ -0,0 +1,23 @@ +{ + "root": "f4157bb27bcb1d1a63001434a249a80948f2e9fe1f53d551244c1dae826b5b23", + "accounts": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "4276951709", + "nonce": 1, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "6916764286133345652", + "nonce": 172, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "42500", + "nonce": 0, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/alloc.json b/cmd/evm/testdata/1/alloc.json new file mode 100644 index 0000000000..cef1a25ff0 --- /dev/null +++ b/cmd/evm/testdata/1/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/env.json b/cmd/evm/testdata/1/env.json new file mode 100644 index 0000000000..dd60abd205 --- /dev/null +++ b/cmd/evm/testdata/1/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/txs.json b/cmd/evm/testdata/1/txs.json new file mode 100644 index 0000000000..50b31ff31b --- /dev/null +++ b/cmd/evm/testdata/1/txs.json @@ -0,0 +1,26 @@ +[ + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + }, + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + } +] diff --git a/cmd/evm/testdata/2/alloc.json b/cmd/evm/testdata/2/alloc.json new file mode 100644 index 0000000000..a9720afc93 --- /dev/null +++ b/cmd/evm/testdata/2/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x6001600053600160006001f0ff00", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/env.json b/cmd/evm/testdata/2/env.json new file mode 100644 index 0000000000..ebadd3f06a --- /dev/null +++ b/cmd/evm/testdata/2/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8" +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/readme.md b/cmd/evm/testdata/2/readme.md new file mode 100644 index 0000000000..c116f0e792 --- /dev/null +++ b/cmd/evm/testdata/2/readme.md @@ -0,0 +1 @@ +These files examplify a selfdestruct to the `0`-address. \ No newline at end of file diff --git a/cmd/evm/testdata/2/txs.json b/cmd/evm/testdata/2/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/2/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/3/alloc.json b/cmd/evm/testdata/3/alloc.json new file mode 100644 index 0000000000..dca318ee54 --- /dev/null +++ b/cmd/evm/testdata/3/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600140", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/env.json b/cmd/evm/testdata/3/env.json new file mode 100644 index 0000000000..e283eff461 --- /dev/null +++ b/cmd/evm/testdata/3/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md new file mode 100644 index 0000000000..499f03d7aa --- /dev/null +++ b/cmd/evm/testdata/3/readme.md @@ -0,0 +1,2 @@ +These files examplify a transition where a transaction (excuted on block 5) requests +the blockhash for block `1`. diff --git a/cmd/evm/testdata/3/txs.json b/cmd/evm/testdata/3/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/3/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/4/alloc.json b/cmd/evm/testdata/4/alloc.json new file mode 100644 index 0000000000..fadf2bdc4e --- /dev/null +++ b/cmd/evm/testdata/4/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600340", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/env.json b/cmd/evm/testdata/4/env.json new file mode 100644 index 0000000000..e283eff461 --- /dev/null +++ b/cmd/evm/testdata/4/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md new file mode 100644 index 0000000000..08840d37bd --- /dev/null +++ b/cmd/evm/testdata/4/readme.md @@ -0,0 +1,3 @@ +These files examplify a transition where a transaction (excuted on block 5) requests +the blockhash for block `4`, but where the hash for that block is missing. +It's expected that executing these should cause `exit` with errorcode `4`. diff --git a/cmd/evm/testdata/4/txs.json b/cmd/evm/testdata/4/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/4/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/5/alloc.json b/cmd/evm/testdata/5/alloc.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/cmd/evm/testdata/5/alloc.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cmd/evm/testdata/5/env.json b/cmd/evm/testdata/5/env.json new file mode 100644 index 0000000000..1085f63e62 --- /dev/null +++ b/cmd/evm/testdata/5/env.json @@ -0,0 +1,11 @@ +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} \ No newline at end of file diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md new file mode 100644 index 0000000000..e2b608face --- /dev/null +++ b/cmd/evm/testdata/5/readme.md @@ -0,0 +1 @@ +These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file diff --git a/cmd/evm/testdata/5/txs.json b/cmd/evm/testdata/5/txs.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/cmd/evm/testdata/5/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/7/alloc.json b/cmd/evm/testdata/7/alloc.json new file mode 100644 index 0000000000..cef1a25ff0 --- /dev/null +++ b/cmd/evm/testdata/7/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/env.json b/cmd/evm/testdata/7/env.json new file mode 100644 index 0000000000..8fd9bc041b --- /dev/null +++ b/cmd/evm/testdata/7/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "5", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/readme.md b/cmd/evm/testdata/7/readme.md new file mode 100644 index 0000000000..c9826e0ba6 --- /dev/null +++ b/cmd/evm/testdata/7/readme.md @@ -0,0 +1,7 @@ +This is a test for HomesteadToDao, checking if the +DAO-transition works + +Example: +``` +./statet8n --input.alloc=./testdata/7/alloc.json --input.txs=./testdata/7/txs.json --input.env=./testdata/7/env.json --output.alloc=stdout --state.fork=HomesteadToDaoAt5 +``` \ No newline at end of file diff --git a/cmd/evm/testdata/7/txs.json b/cmd/evm/testdata/7/txs.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/cmd/evm/testdata/7/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh new file mode 100644 index 0000000000..d1400ca577 --- /dev/null +++ b/cmd/evm/transition-test.sh @@ -0,0 +1,191 @@ +#!/bin/bash +ticks="\`\`\`" + +function showjson(){ + echo "\`$1\`:" + echo "${ticks}json" + cat $1 + echo "" + echo "$ticks" +} +function demo(){ + echo "$ticks" + echo "$1" + echo "$ticks" + echo "" +} +function tick(){ + echo "$ticks" +} + +cat << EOF +## EVM state transition tool + +The \`evm t8n\` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +## Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based +implementation. + +### Command line params + +Command line params that has to be supported are +$(tick) + +` ./evm t8n -h | grep "trace\|output\|state\."` + +$(tick) + +### Error codes and output + +All logging should happen against the \`stderr\`. +There are a few (not many) errors that can occur, those are defined below. + +#### EVM-based errors (\`2\` to \`9\`) + +- Other EVM error. Exit code \`2\` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code \`3\`. +- Block history is not supplied, but needed for a \`BLOCKHASH\` operation. If \`BLOCKHASH\` + is invoked targeting a block which history has not been provided for, the program will + exit with code \`4\`. + +#### IO errors (\`10\`-\`20\`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code \`10\` +- IO problems: failure to load or save files, the program will exit with code \`11\` + +EOF + +# This should exit with 3 +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +if [ $? != 3 ]; then + echo "Failed, exitcode should be 3" +fi +cat << EOF +## Examples +### Basic usage + +Invoking it with the provided example files +EOF +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json" +tick;echo "$cmd"; tick +$cmd 2>/dev/null +echo "Two resulting files:" +echo "" +showjson alloc.json +showjson result.json +echo "" + +echo "We can make them spit out the data to e.g. \`stdout\` like this:" +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout" +tick;echo "$cmd"; tick +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +cat << EOF + +## About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- \`block_reward\` is the block mining reward for the miner (\`0xaa\`), of a block at height \`N\`. +- For each ommer (mined by \`0xbb\`), with blocknumber \`N-delta\` + - (where \`delta\` is the difference between the current block and the ommer) + - The account \`0xbb\` (ommer miner) is awarded \`(8-delta)/ 8 * block_reward\` + - The account \`0xaa\` (block miner) is awarded \`block_reward / 32\` + +To make \`state_t8n\` apply these, the following inputs are required: + +- \`state.reward\` + - For ethash, it is \`5000000000000000000\` \`wei\`, + - If this is not defined, mining rewards are not applied, + - A value of \`0\` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an \`address\` and a \`delta\`. This + is done via the \`env\`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +EOF + +showjson ./testdata/5/env.json + +echo "When applying this, using a reward of \`0x08\`" +cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80" +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +echo "### Future EIPS" +echo "" +echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." +echo "Example, putting EIP-1344 into Frontier: " +cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json" +tick;echo "$cmd"; tick +echo "" + +echo "### Block history" +echo "" +echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." +echo "If a required blockhash is not provided, the exit code should be \`4\`:" +echo "Example where blockhashes are provided: " +cmd="./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace" +tick && echo $cmd && tick +$cmd 2>&1 >/dev/null +cmd="cat trace-0.jsonl | grep BLOCKHASH -C2" +tick && echo $cmd && tick +echo "$ticks" +cat trace-0.jsonl | grep BLOCKHASH -C2 +echo "$ticks" +echo "" + +echo "In this example, the caller has not provided the required blockhash:" +cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace" +tick && echo $cmd && tick +tick +$cmd +errc=$? +tick +echo "Error code: $errc" + + +echo "### Chaining" +echo "" +echo "Another thing that can be done, is to chain invocations:" +cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout" +cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json" +echo "$ticks" +echo "$cmd1 | $cmd2" +output=$($cmd1 | $cmd2 ) +echo $output +echo "$ticks" +echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. " +echo "Then, taking the poststate alloc as the input for the next state, we tried again to include" +echo "the same two transactions: this time, both failed due to too low nonce." +echo "" +echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the" +echo "actual blocknumber (exposed to the EVM) would not increase." +echo "" diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index cd5eca3531..85bb0b78f9 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -699,7 +699,8 @@ func authTwitter(url string) (string, string, common.Address, error) { // the mobile page though since the main page loads tweet contents via JS. // #nosec (we don't use faucet) url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1) - //nolint:gosec G107 this function is called with a twitter url + // gosec/G107. This function is called with a twitter url, no need to run gosec. + //nolint:gosec res, err := http.Get(url) if err != nil { return "", "", common.Address{}, err diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index 598790a306..3ed676d5ec 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -26,7 +26,6 @@ import ( "github.com/celo-org/celo-blockchain/accounts/usbwallet" "github.com/celo-org/celo-blockchain/cmd/utils" "github.com/celo-org/celo-blockchain/common" - prompt2 "github.com/celo-org/celo-blockchain/console/prompt" "github.com/celo-org/celo-blockchain/crypto" "github.com/celo-org/celo-blockchain/log" cli "gopkg.in/urfave/cli.v1" @@ -382,7 +381,7 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str } for trials := 0; trials < 3; trials++ { prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) - password := getPassPhrase(prompt, false, i, passwords) + password := utils.GetPassPhraseWithList(prompt, false, i, passwords) err = ks.Unlock(account, password) if err == nil { log.Info("Unlocked account", "address", account.Address.Hex()) @@ -403,36 +402,6 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str return accounts.Account{}, "" } -// getPassPhrase retrieves the password associated with an account, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { - // If a list of passwords was supplied, retrieve from them - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - // Otherwise prompt the user for the password - if prompt != "" { - fmt.Println(prompt) - } - password, err := prompt2.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - if confirmation { - confirm, err := prompt2.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passwords do not match") - } - } - return password -} - func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) for _, a := range err.Matches { @@ -475,7 +444,7 @@ func accountCreate(ctx *cli.Context) error { utils.Fatalf("Failed to read configuration: %v", err) } - password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + password := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) @@ -503,7 +472,7 @@ func accountUpdate(ctx *cli.Context) error { for _, addr := range ctx.Args() { account, oldPassword := unlockAccount(ks, addr, 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) + newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) if err := ks.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } @@ -522,7 +491,7 @@ func importWallet(ctx *cli.Context) error { } stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhraseWithList("", false, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) @@ -543,7 +512,7 @@ func accountImport(ctx *cli.Context) error { utils.Fatalf("Failed to load the private key: %v", err) } stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportECDSA(key, passphrase) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index e1ac309d1c..5d9b404521 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -87,6 +87,8 @@ The dumpgenesis command dumps the genesis block configuration in JSON format to utils.CacheGCFlag, utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, utils.MetricsEnableInfluxDBFlag, utils.MetricsInfluxDBEndpointFlag, utils.MetricsInfluxDBDatabaseFlag, @@ -305,8 +307,10 @@ func importChain(ctx *cli.Context) error { // Import the chain start := time.Now() + var importErr error for _, arg := range ctx.Args() { if err := utils.ImportChain(chain, arg); err != nil { + importErr = err log.Error("Import error", "file", arg, "err", err) } } @@ -358,7 +362,7 @@ func importChain(ctx *cli.Context) error { utils.Fatalf("Failed to read database iostats: %v", err) } fmt.Println(ioStats) - return nil + return importErr } func exportChain(ctx *cli.Context) error { diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 730fc612fa..9a1671721f 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -104,7 +104,7 @@ func TestHTTPAttachWelcome(t *testing.T) { port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P geth := runGeth(t, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--light.maxpeers", "0", - "--etherbase", coinbase, "--rpc", "--rpcport", port) + "--etherbase", coinbase, "--http", "--http.port", port) defer func() { geth.Interrupt() geth.ExpectExit() @@ -121,7 +121,7 @@ func TestWSAttachWelcome(t *testing.T) { geth := runGeth(t, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--light.maxpeers", "0", - "--etherbase", coinbase, "--ws", "--wsport", port) + "--etherbase", coinbase, "--ws", "--ws.port", port) defer func() { geth.Interrupt() geth.ExpectExit() diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go new file mode 100644 index 0000000000..1f517219cc --- /dev/null +++ b/cmd/geth/les_test.go @@ -0,0 +1,147 @@ +package main + +import ( + "context" + "path/filepath" + "testing" + "time" + + "github.com/celo-org/celo-blockchain/p2p" + "github.com/celo-org/celo-blockchain/rpc" +) + +type gethrpc struct { + name string + rpc *rpc.Client + geth *testgeth + nodeInfo *p2p.NodeInfo +} + +func (g *gethrpc) killAndWait() { + g.geth.Kill() + g.geth.WaitExit() +} + +func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) { + if err := g.rpc.Call(&result, method, args...); err != nil { + g.geth.Fatalf("callRPC %v: %v", method, err) + } +} + +func (g *gethrpc) addPeer(peer *gethrpc) { + g.geth.Logf("%v.addPeer(%v)", g.name, peer.name) + enode := peer.getNodeInfo().Enode + peerCh := make(chan *p2p.PeerEvent) + sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents") + if err != nil { + g.geth.Fatalf("subscribe %v: %v", g.name, err) + } + defer sub.Unsubscribe() + g.callRPC(nil, "admin_addPeer", enode) + dur := 14 * time.Second + timeout := time.After(dur) + select { + case ev := <-peerCh: + g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer) + case err := <-sub.Err(): + g.geth.Fatalf("%v sub error: %v", g.name, err) + case <-timeout: + g.geth.Error("timeout adding peer after", dur) + } +} + +// Use this function instead of `g.nodeInfo` directly +func (g *gethrpc) getNodeInfo() *p2p.NodeInfo { + if g.nodeInfo != nil { + return g.nodeInfo + } + g.nodeInfo = &p2p.NodeInfo{} + g.callRPC(&g.nodeInfo, "admin_nodeInfo") + return g.nodeInfo +} + +func startGethWithRpc(t *testing.T, name string, args ...string) *gethrpc { + g := &gethrpc{name: name} + args = append([]string{"--networkid=42", "--port=0", "--nousb", "--http", "--http.port=0", "--http.api=admin,eth,les"}, args...) + t.Logf("Starting %v with rpc: %v", name, args) + g.geth = runGeth(t, args...) + // wait before we can attach to it. TODO: probe for it properly + time.Sleep(1 * time.Second) + var err error + ipcpath := filepath.Join(g.geth.Datadir, "geth.ipc") + g.rpc, err = rpc.Dial(ipcpath) + if err != nil { + t.Fatalf("%v rpc connect: %v", name, err) + } + return g +} + +func initGeth(t *testing.T) string { + g := runGeth(t, "--networkid=42", "init", "./testdata/clique.json") + datadir := g.Datadir + g.WaitExit() + return datadir +} + +func startLightServer(t *testing.T) *gethrpc { + datadir := initGeth(t) + runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit() + account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" + server := startGethWithRpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", + "--unlock", account, "--mine", "--miner.validator", account, "--tx-fee-recipient", account, + "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1") + return server +} + +func startClient(t *testing.T, name string) *gethrpc { + datadir := initGeth(t) + return startGethWithRpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1") +} + +func TestPriorityClient(t *testing.T) { + lightServer := startLightServer(t) + defer lightServer.killAndWait() + + // Start client and add lightServer as peer + freeCli := startClient(t, "freeCli") + defer freeCli.killAndWait() + freeCli.addPeer(lightServer) + + var peers []*p2p.PeerInfo + freeCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { + t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers)) + return + } + + // Set up priority client, get its nodeID, increase its balance on the lightServer + prioCli := startClient(t, "prioCli") + defer prioCli.killAndWait() + // 3_000_000_000 once we move to Go 1.13 + tokens := 3000000000 + lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens, "foobar") + prioCli.addPeer(lightServer) + + // Check if priority client is actually syncing and the regular client got kicked out + prioCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { + t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers)) + } + + nodes := map[string]*gethrpc{ + lightServer.getNodeInfo().ID: lightServer, + freeCli.getNodeInfo().ID: freeCli, + prioCli.getNodeInfo().ID: prioCli, + } + lightServer.callRPC(&peers, "admin_peers") + peersWithNames := make(map[string]string) + for _, p := range peers { + peersWithNames[nodes[p.ID].name] = p.ID + } + if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound { + t.Error("client is still a peer of lightServer", peersWithNames) + } + if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound { + t.Error("prio client is not among lightServer peers", peersWithNames) + } +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ea8105598e..be20f66493 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -182,6 +182,7 @@ var ( utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCap, + utils.RPCGlobalTxFeeCap, } whisperFlags = []cli.Flag{ @@ -194,6 +195,8 @@ var ( metricsFlags = []cli.Flag{ utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, utils.MetricsEnableInfluxDBFlag, utils.MetricsInfluxDBEndpointFlag, utils.MetricsInfluxDBDatabaseFlag, diff --git a/cmd/geth/testdata/blockchain.blocks b/cmd/geth/testdata/blockchain.blocks new file mode 100644 index 0000000000..d29453d3e5 Binary files /dev/null and b/cmd/geth/testdata/blockchain.blocks differ diff --git a/cmd/geth/testdata/clique.json b/cmd/geth/testdata/clique.json new file mode 100644 index 0000000000..00d1e6666a --- /dev/null +++ b/cmd/geth/testdata/clique.json @@ -0,0 +1,22 @@ +{ + "config": { + "chainId": 15, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbul": { + } + }, + "difficulty": "1", + "gasLimit": "8000000", + "extraData": "0xecc833a7747eaa8327335e8e0c6b6d8aa3a38d0063591e43ce116ccf5c89753ef90262f869945296a071434eb2943c1bde8a2e1d555d911cd1d594bc236d14fbbe74b4d3bc132309b861dcf1c527c594ab3233a00d1e03b3e0c7c9912421b56f498901ef9421d87e9445d3b355807c91937e70e6f9914f2a1d94f6a964ad845a8a8a4a50b884c140ab8c3ed8252cf901eab86056631e24a6ec7fb89b5d967b6e7169eed00c0d802b9050d5a749b9d69a2c70d047b5add1d78646c4c3d579e7da7691001f0c832e9b26dd02b960b610ebfa63cd3a9dc0d6da8abfd8c16ffc5679debc682216c2934af7dc93d82e4a42564b4d80b86065be0e12d1ac62a4ddf326cb0dde804d8c1aab376dff994c9db706c6a5c0d5ba57319105491658f9ae5d5a4f48752b006137148123d8a526b2237d7284d5f0ab664f186ef0e7649d586a9754f54ca042539b3ba7ea25d0209f639cce30262081b860f1dade6f52a125a236a546a5bf5468ef8c95da980a51ee2ea595919e80bb56b3941bd17315b2681a411b6f7b6a2aaa01815a62fff57a14cae0cbef5a540dbd34f098ae18c07f93137eefc25132ac1971c8e74f2ddf24ceeeece87dcd18f19500b8601e4c01b96874cbc25fe98a4a8300035865d02724ec7b62d1662eba6b49777aaad51b73eb228d136d8d1f436e391e7e01d2c597cfcc17465a3aa1951d610360365bb116ab759887d5a74064c663aeeb3facf55ddc9a212e9f06b9925d27011700b860306ff63a1746b45b891a33d8ad7a02c92bf0ec628499f5308c55b15e7b5a2ab3d97113ef669c5ae446cb69b965c28d0056c780a4f2462b989bcb15380cc71fcf163d7e7be97c8c2a70b72e8273ff87f7d249f3c552fe7ecc28331f4ce90d92808080c3808080c3808080", + "alloc": { + "02f0d131f1f97aef08aec6e3291b957d9efe7105": { + "balance": "300000" + } + } +} \ No newline at end of file diff --git a/cmd/geth/testdata/key.prv b/cmd/geth/testdata/key.prv new file mode 100644 index 0000000000..1d2687ea63 --- /dev/null +++ b/cmd/geth/testdata/key.prv @@ -0,0 +1 @@ +48aa455c373ec5ce7fefb0e54f44a215decdc85b9047bc4d09801e038909bdbe \ No newline at end of file diff --git a/cmd/geth/testdata/password.txt b/cmd/geth/testdata/password.txt new file mode 100644 index 0000000000..f6ea049518 --- /dev/null +++ b/cmd/geth/testdata/password.txt @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 436e3ed15c..a09b81cbd1 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -162,6 +162,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.RPCGlobalGasCap, + utils.RPCGlobalTxFeeCap, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d110c4d9e8..c1a9b23db3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,7 +23,6 @@ import ( "fmt" "io" "io/ioutil" - "math/big" "os" "path/filepath" "strconv" @@ -49,6 +48,7 @@ import ( "github.com/celo-org/celo-blockchain/les" "github.com/celo-org/celo-blockchain/log" "github.com/celo-org/celo-blockchain/metrics" + "github.com/celo-org/celo-blockchain/metrics/exp" "github.com/celo-org/celo-blockchain/metrics/influxdb" "github.com/celo-org/celo-blockchain/miner" "github.com/celo-org/celo-blockchain/node" @@ -68,8 +68,8 @@ var ( {{if .cmd.Description}}{{.cmd.Description}} {{end}}{{if .cmd.Subcommands}} SUBCOMMANDS: - {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .categorizedFlags}} + {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .categorizedFlags}} {{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: {{range $categorized.Flags}}{{"\t"}}{{.}} {{end}} @@ -79,10 +79,10 @@ SUBCOMMANDS: {{if .Description}}{{.Description}} {{end}}{{if .Subcommands}} SUBCOMMANDS: - {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .Flags}} + {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} OPTIONS: -{{range $.Flags}}{{"\t"}}{{.}} +{{range $.Flags}} {{.}} {{end}} {{end}}` ) @@ -430,9 +430,14 @@ var ( } RPCGlobalGasCap = cli.Uint64Flag{ Name: "rpc.gascap", - Usage: "Sets a cap on gas that can be used in eth_call/estimateGas", + Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", + Value: eth.DefaultConfig.RPCGasCap, + } + RPCGlobalTxFeeCap = cli.Float64Flag{ + Name: "rpc.txfeecap", + Usage: "Sets a cap on transaction fee (in celo) that can be sent via the RPC APIs (0 = no cap)", + Value: eth.DefaultConfig.RPCTxFeeCap, } - // Logging and debug settings CeloStatsURLFlag = cli.StringFlag{ @@ -640,6 +645,21 @@ var ( Name: "metrics.expensive", Usage: "Enable expensive metrics collection and reporting", } + + // MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint. + // Since the pprof service enables sensitive/vulnerable behavior, this allows a user + // to enable a public-OK metrics endpoint without having to worry about ALSO exposing + // other profiling behavior or information. + MetricsHTTPFlag = cli.StringFlag{ + Name: "metrics.addr", + Usage: "Enable stand-alone metrics HTTP server listening interface", + Value: "127.0.0.1", + } + MetricsPortFlag = cli.IntFlag{ + Name: "metrics.port", + Usage: "Metrics HTTP server listening port", + Value: 6060, + } MetricsEnableInfluxDBFlag = cli.BoolFlag{ Name: "metrics.influxdb", Usage: "Enable metrics export/push to an external InfluxDB database", @@ -881,12 +901,15 @@ func setNAT(ctx *cli.Context, cfg *p2p.Config) { // splitAndTrim splits input separated by a comma // and trims excessive white space from the substrings. -func splitAndTrim(input string) []string { - result := strings.Split(input, ",") - for i, r := range result { - result[i] = strings.TrimSpace(r) +func splitAndTrim(input string) (ret []string) { + l := strings.Split(input, ",") + for _, r := range l { + r = strings.TrimSpace(r) + if len(r) > 0 { + ret = append(ret, r) + } } - return result + return ret } // setHTTP creates the HTTP RPC listener interface string from the set @@ -1658,8 +1681,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { - cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) + cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCap.Name) + } + if cfg.RPCGasCap != 0 { + log.Info("Set global gas cap", "cap", cfg.RPCGasCap) + } else { + log.Info("Global gas cap disabled") + } + + if ctx.GlobalIsSet(RPCGlobalTxFeeCap.Name) { + cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCap.Name) } + // Disable DNS discovery by default (by using the flag's value even if it hasn't been set and so // has the default value ""), since we don't have DNS discovery set up for Celo. // Note that passing --discovery.dns "" is the way the Geth docs specify for disabling DNS discovery, @@ -1804,6 +1837,7 @@ func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []st func SetupMetrics(ctx *cli.Context) { if metrics.Enabled { log.Info("Enabling metrics collection") + var ( enableExport = ctx.GlobalBool(MetricsEnableInfluxDBFlag.Name) endpoint = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name) @@ -1819,6 +1853,12 @@ func SetupMetrics(ctx *cli.Context) { go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) } + + if ctx.GlobalIsSet(MetricsHTTPFlag.Name) { + address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name)) + log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) + exp.Setup(address) + } } } diff --git a/cmd/utils/prompt.go b/cmd/utils/prompt.go new file mode 100644 index 0000000000..20ecd55f69 --- /dev/null +++ b/cmd/utils/prompt.go @@ -0,0 +1,62 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "fmt" + + "github.com/celo-org/celo-blockchain/console/prompt" +) + +// GetPassPhrase displays the given text(prompt) to the user and requests some textual +// data to be entered, but one which must not be echoed out into the terminal. +// The method returns the input provided by the user. +func GetPassPhrase(text string, confirmation bool) string { + if text != "" { + fmt.Println(text) + } + password, err := prompt.Stdin.PromptPassword("Password: ") + if err != nil { + Fatalf("Failed to read password: %v", err) + } + if confirmation { + confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") + if err != nil { + Fatalf("Failed to read password confirmation: %v", err) + } + if password != confirm { + Fatalf("Passwords do not match") + } + } + return password +} + +// GetPassPhraseWithList retrieves the password associated with an account, either fetched +// from a list of preloaded passphrases, or requested interactively from the user. +func GetPassPhraseWithList(text string, confirmation bool, index int, passwords []string) string { + // If a list of passwords was supplied, retrieve from them + if len(passwords) > 0 { + if index < len(passwords) { + return passwords[index] + } + return passwords[len(passwords)-1] + } + // Otherwise prompt the user for the password + password := GetPassPhrase(text, confirmation) + return password +} diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go new file mode 100644 index 0000000000..62ea75a3f6 --- /dev/null +++ b/cmd/utils/prompt_test.go @@ -0,0 +1,74 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "testing" +) + +func TestGetPassPhraseWithList(t *testing.T) { + type args struct { + text string + confirmation bool + index int + passwords []string + } + tests := []struct { + name string + args args + want string + }{ + { + "test1", + args{ + "text1", + false, + 0, + []string{"zero", "one", "two"}, + }, + "zero", + }, + { + "test2", + args{ + "text2", + false, + 5, + []string{"zero", "one", "two"}, + }, + "two", + }, + { + "test3", + args{ + "text3", + true, + 1, + []string{"zero", "one", "two"}, + }, + "one", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want { + t.Errorf("GetPassPhraseWithList() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/common/fdlimit/fdlimit_freebsd.go b/common/fdlimit/fdlimit_bsd.go similarity index 96% rename from common/fdlimit/fdlimit_freebsd.go rename to common/fdlimit/fdlimit_bsd.go index 0d8727138e..86181337a2 100644 --- a/common/fdlimit/fdlimit_freebsd.go +++ b/common/fdlimit/fdlimit_bsd.go @@ -14,14 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build freebsd +// +build freebsd dragonfly package fdlimit import "syscall" // This file is largely identical to fdlimit_unix.go, -// but Rlimit fields have type int64 on FreeBSD so it needs +// but Rlimit fields have type int64 on *BSD so it needs // an extra conversion. // Raise tries to maximize the file descriptor allowance of this process diff --git a/common/math/integer.go b/common/math/integer.go index 93b1d036dd..50d3eba1f5 100644 --- a/common/math/integer.go +++ b/common/math/integer.go @@ -18,6 +18,7 @@ package math import ( "fmt" + "math/bits" "strconv" ) @@ -78,22 +79,20 @@ func MustParseUint64(s string) uint64 { return v } -// NOTE: The following methods need to be optimised using either bit checking or asm - -// SafeSub returns subtraction result and whether overflow occurred. +// SafeSub returns x-y and checks for overflow. func SafeSub(x, y uint64) (uint64, bool) { - return x - y, x < y + diff, borrowOut := bits.Sub64(x, y, 0) + return diff, borrowOut != 0 } -// SafeAdd returns the result and whether overflow occurred. +// SafeAdd returns x+y and checks for overflow. func SafeAdd(x, y uint64) (uint64, bool) { - return x + y, y > MaxUint64-x + sum, carryOut := bits.Add64(x, y, 0) + return sum, carryOut != 0 } -// SafeMul returns multiplication result and whether overflow occurred. +// SafeMul returns x*y and checks for overflow. func SafeMul(x, y uint64) (uint64, bool) { - if x == 0 || y == 0 { - return 0, false - } - return x * y, y > MaxUint64/x + hi, lo := bits.Mul64(x, y) + return lo, hi != 0 } diff --git a/console/console.go b/console/console.go index 0a36b83282..2a1112b406 100644 --- a/console/console.go +++ b/console/console.go @@ -39,7 +39,8 @@ import ( ) var ( - passwordRegexp = regexp.MustCompile(`personal.[nus]`) + // u: unlock, s: signXX, sendXX, n: newAccount, i: importXX + passwordRegexp = regexp.MustCompile(`personal.[nusi]`) onlyWhitespace = regexp.MustCompile(`^\s*$`) exit = regexp.MustCompile(`^\s*exit\s*;*\s*$`) ) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 3d4bfc6f7a..597c082079 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -333,7 +333,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { var ( start = time.Now() first = f.frozen - ancients = make([]common.Hash, 0, limit) + ancients = make([]common.Hash, 0, limit-f.frozen) ) for f.frozen <= limit { // Retrieves all the components of the canonical block diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index e14c6b920c..93e730fb30 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -232,8 +232,8 @@ func (t *freezerTable) repair() error { t.index.ReadAt(buffer, 0) firstIndex.unmarshalBinary(buffer) - t.tailId = firstIndex.offset - t.itemOffset = firstIndex.filenum + t.tailId = firstIndex.filenum + t.itemOffset = firstIndex.offset t.index.ReadAt(buffer, offsetsSize-indexEntrySize) lastIndex.unmarshalBinary(buffer) @@ -519,16 +519,27 @@ func (t *freezerTable) Append(item uint64, blob []byte) error { // getBounds returns the indexes for the item // returns start, end, filenumber and error func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) { - var startIdx, endIdx indexEntry buffer := make([]byte, indexEntrySize) - if _, err := t.index.ReadAt(buffer, int64(item*indexEntrySize)); err != nil { - return 0, 0, 0, err - } - startIdx.unmarshalBinary(buffer) + var startIdx, endIdx indexEntry + // Read second index if _, err := t.index.ReadAt(buffer, int64((item+1)*indexEntrySize)); err != nil { return 0, 0, 0, err } endIdx.unmarshalBinary(buffer) + // Read first index (unless it's the very first item) + if item != 0 { + if _, err := t.index.ReadAt(buffer, int64(item*indexEntrySize)); err != nil { + return 0, 0, 0, err + } + startIdx.unmarshalBinary(buffer) + } else { + // Special case if we're reading the first item in the freezer. We assume that + // the first item always start from zero(regarding the deletion, we + // only support deletion by files, so that the assumption is held). + // This means we can use the first item metadata to carry information about + // the 'global' offset, for the deletion-case + return 0, endIdx.offset, endIdx.filenum, nil + } if startIdx.filenum != endIdx.filenum { // If a piece of data 'crosses' a data-file, // it's actually in one piece on the second data-file. diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 3ebfad4e3d..55cf7adb9e 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -564,8 +564,8 @@ func TestOffset(t *testing.T) { tailId := uint32(2) // First file is 2 itemOffset := uint32(4) // We have removed four items zeroIndex := indexEntry{ - offset: tailId, - filenum: itemOffset, + filenum: tailId, + offset: itemOffset, } buf := zeroIndex.marshallBinary() // Overwrite index zero @@ -579,39 +579,67 @@ func TestOffset(t *testing.T) { } // Now open again - { + checkPresent := func(numDeleted uint64) { f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true) if err != nil { t.Fatal(err) } f.printIndex() // It should allow writing item 6 - f.Append(6, getChunk(20, 0x99)) + f.Append(numDeleted+2, getChunk(20, 0x99)) // It should be fine to fetch 4,5,6 - if got, err := f.Retrieve(4); err != nil { + if got, err := f.Retrieve(numDeleted); err != nil { t.Fatal(err) } else if exp := getChunk(20, 0xbb); !bytes.Equal(got, exp) { t.Fatalf("expected %x got %x", exp, got) } - if got, err := f.Retrieve(5); err != nil { + if got, err := f.Retrieve(numDeleted + 1); err != nil { t.Fatal(err) } else if exp := getChunk(20, 0xaa); !bytes.Equal(got, exp) { t.Fatalf("expected %x got %x", exp, got) } - if got, err := f.Retrieve(6); err != nil { + if got, err := f.Retrieve(numDeleted + 2); err != nil { t.Fatal(err) } else if exp := getChunk(20, 0x99); !bytes.Equal(got, exp) { t.Fatalf("expected %x got %x", exp, got) } // It should error at 0, 1,2,3 - for i := 0; i < 4; i++ { - if _, err := f.Retrieve(uint64(i)); err == nil { + for i := numDeleted - 1; i > numDeleted-10; i-- { + if _, err := f.Retrieve(i); err == nil { t.Fatal("expected err") } } } + checkPresent(4) + // Now, let's pretend we have deleted 1M items + { + // Read the index file + p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname)) + indexFile, err := os.OpenFile(p, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + indexBuf := make([]byte, 3*indexEntrySize) + indexFile.Read(indexBuf) + + // Update the index file, so that we store + // [ file = 2, offset = 1M ] at index zero + + tailId := uint32(2) // First file is 2 + itemOffset := uint32(1000000) // We have removed 1M items + zeroIndex := indexEntry{ + offset: itemOffset, + filenum: tailId, + } + buf := zeroIndex.marshallBinary() + // Overwrite index zero + copy(indexBuf, buf) + indexFile.WriteAt(indexBuf, 0) + indexFile.Close() + } + checkPresent(1000000) } // TODO (?) diff --git a/core/state/dump.go b/core/state/dump.go index 051476c8a8..65c509a1da 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -27,6 +27,14 @@ import ( "github.com/celo-org/celo-blockchain/trie" ) +// DumpCollector interface which the state trie calls during iteration +type DumpCollector interface { + // OnRoot is called with the state root + OnRoot(common.Hash) + // OnAccount is called once for each account in the trie + OnAccount(common.Address, DumpAccount) +} + // DumpAccount represents an account in the state. type DumpAccount struct { Balance string `json:"balance"` @@ -46,9 +54,14 @@ type Dump struct { Accounts map[common.Address]DumpAccount `json:"accounts"` } -// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively. -type iterativeDump struct { - *json.Encoder +// OnRoot implements DumpCollector interface +func (d *Dump) OnRoot(root common.Hash) { + d.Root = fmt.Sprintf("%x", root) +} + +// OnAccount implements DumpCollector interface +func (d *Dump) OnAccount(addr common.Address, account DumpAccount) { + d.Accounts[addr] = account } // IteratorDump is an implementation for iterating over data. @@ -58,28 +71,23 @@ type IteratorDump struct { Next []byte `json:"next,omitempty"` // nil if no more accounts } -// Collector interface which the state trie calls during iteration -type collector interface { - onRoot(common.Hash) - onAccount(common.Address, DumpAccount) -} - -func (d *Dump) onRoot(root common.Hash) { +// OnRoot implements DumpCollector interface +func (d *IteratorDump) OnRoot(root common.Hash) { d.Root = fmt.Sprintf("%x", root) } -func (d *Dump) onAccount(addr common.Address, account DumpAccount) { +// OnAccount implements DumpCollector interface +func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) { d.Accounts[addr] = account } -func (d *IteratorDump) onRoot(root common.Hash) { - d.Root = fmt.Sprintf("%x", root) -} -func (d *IteratorDump) onAccount(addr common.Address, account DumpAccount) { - d.Accounts[addr] = account +// iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively. +type iterativeDump struct { + *json.Encoder } -func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) { +// OnAccount implements DumpCollector interface +func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ Balance: account.Balance, Nonce: account.Nonce, @@ -96,15 +104,16 @@ func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) { d.Encode(dumpAccount) } -func (d iterativeDump) onRoot(root common.Hash) { +// OnRoot implements DumpCollector interface +func (d iterativeDump) OnRoot(root common.Hash) { d.Encode(struct { Root common.Hash `json:"root"` }{root}) } -func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { +func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { missingPreimages := 0 - c.onRoot(s.trie.Hash()) + c.OnRoot(s.trie.Hash()) var count int it := trie.NewIterator(s.trie.NodeIterator(start)) @@ -145,7 +154,7 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) } } - c.onAccount(addr, account) + c.OnAccount(addr, account) count++ if maxResults > 0 && count >= maxResults { if it.Next() { @@ -166,7 +175,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b dump := &Dump{ Accounts: make(map[common.Address]DumpAccount), } - s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) + s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) return *dump } @@ -175,14 +184,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages) json, err := json.MarshalIndent(dump, "", " ") if err != nil { - fmt.Println("dump err", err) + fmt.Println("Dump err", err) } return json } // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) { - s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) + s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) } // IteratorDump dumps out a batch of accounts starts with the given start key @@ -190,6 +199,6 @@ func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreima iterator := &IteratorDump{ Accounts: make(map[common.Address]DumpAccount), } - iterator.Next = s.dump(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults) + iterator.Next = s.DumpToCollector(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults) return *iterator } diff --git a/core/state/state_object.go b/core/state/state_object.go index aae44abd53..02d28fa713 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -213,14 +213,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if _, destructed := s.db.snapDestructs[s.addrHash]; destructed { return common.Hash{} } - enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) + enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) } // If snapshot unavailable or reading from it failed, load from the database if s.db.snap == nil || err != nil { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) } - if enc, err = s.getTrie(db).TryGet(key[:]); err != nil { + if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil { s.setError(err) return common.Hash{} } diff --git a/core/state/state_test.go b/core/state/state_test.go index f9a612a288..fb8a958fe3 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -56,7 +56,7 @@ func TestDump(t *testing.T) { s.state.updateStateObject(obj2) s.state.Commit(false) - // check that dump contains the state objects that are in trie + // check that DumpToCollector contains the state objects that are in trie got := string(s.state.Dump(false, false, true)) want := `{ "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", @@ -83,7 +83,7 @@ func TestDump(t *testing.T) { } }` if got != want { - t.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", got, want) + t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 40805d2e82..554cd146de 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -513,7 +513,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) } var acc *snapshot.Account - if acc, err = s.snap.Account(crypto.Keccak256Hash(addr[:])); err == nil { + if acc, err = s.snap.Account(crypto.Keccak256Hash(addr.Bytes())); err == nil { if acc == nil { return nil } @@ -532,9 +532,9 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if metrics.EnabledExpensive { defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) } - enc, err := s.trie.TryGet(addr[:]) + enc, err := s.trie.TryGet(addr.Bytes()) if err != nil { - s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr[:], err)) + s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err)) return nil } if len(enc) == 0 { diff --git a/core/tx_list.go b/core/tx_list.go index addea4fddd..3763de6c32 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -348,7 +348,7 @@ func (l *txList) Forward(threshold uint64) types.Transactions { // This method uses the cached costcap and gascap to quickly decide if there's even // a point in calculating all the costs or if the balance covers all. If the threshold // is lower than the costgas cap, the caps will be reset to a new high after removing -func (l *txList) Filter(nativeCostLimit *big.Int, feeLimits map[common.Address]*big.Int, blockCtx BlockContext, gasLimit uint64) (types.Transactions, types.Transactions) { +func (l *txList) Filter(nativeCostLimit *big.Int, feeLimits map[common.Address]*big.Int, gasLimit uint64) (types.Transactions, types.Transactions) { // check if we can bail & lower caps & raise floors at the same time canBail := true @@ -503,7 +503,7 @@ func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h priceHeap) Less(i, j int) bool { // Sort primarily by price, returning the cheaper one - switch h[i].GasPrice().Cmp(h[j].GasPrice()) { + switch h[i].GasPriceCmp(h[j]) { case -1: return true case 1: diff --git a/core/tx_pool.go b/core/tx_pool.go index fc5dfb16ac..50a0a1feef 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -892,16 +892,22 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { knownTxMeter.Mark(1) continue } + // Exclude transactions with invalid signatures as soon as + // possible and cache senders in transactions before + // obtaining lock + _, err := types.Sender(pool.signer, tx) + if err != nil { + errs[i] = ErrInvalidSender + invalidTxMeter.Mark(1) + continue + } // Accumulate all unknown transactions for deeper processing news = append(news, tx) } if len(news) == 0 { return errs } - // Cache senders in transactions before obtaining lock (pool.signer is immutable) - for _, tx := range news { - types.Sender(pool.signer, tx) - } + // Process all the new transaction and merge any errors into the original slice pool.mu.Lock() newErrs, dirtyAddrs := pool.addTxsLocked(news, local) @@ -1304,8 +1310,8 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans for _, tx := range forwards { hash := tx.Hash() pool.all.Remove(hash) - log.Trace("Removed old queued transaction", "hash", hash) } + log.Trace("Removed old queued transactions", "count", len(forwards)) // Get balances in each currency balances := make(map[common.Address]*big.Int) allCurrencies := list.FeeCurrencies() @@ -1314,12 +1320,12 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans balances[feeCurrency] = feeCurrencyBalance } // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), balances, pool.ctx().BlockContext, pool.currentMaxGas) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), balances, pool.currentMaxGas) for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) - log.Trace("Removed unpayable queued transaction", "hash", hash) } + log.Trace("Removed unpayable queued transactions", "count", len(drops)) queuedNofundsMeter.Mark(int64(len(drops))) // Gather all executable transactions and promote them @@ -1327,10 +1333,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans for _, tx := range readies { hash := tx.Hash() if pool.promoteTx(addr, hash, tx) { - log.Trace("Promoting queued transaction", "hash", hash) promoted = append(promoted, tx) } } + log.Trace("Promoted queued transactions", "count", len(promoted)) queuedGauge.Dec(int64(len(readies))) // Drop all transactions over the allowed limit @@ -1514,7 +1520,7 @@ func (pool *TxPool) demoteUnexecutables() { balances[feeCurrency] = feeCurrencyBalance } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), balances, pool.ctx().BlockContext, pool.currentMaxGas) + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), balances, pool.currentMaxGas) for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index c1ed5d95ee..f398e8cdb1 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -18,6 +18,7 @@ package core import ( "crypto/ecdsa" + "errors" "fmt" "io/ioutil" "math/big" @@ -274,7 +275,7 @@ func TestInvalidTransactions(t *testing.T) { balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice())) pool.currentState.AddBalance(from, balance) - if err := pool.AddRemote(tx); err != ErrIntrinsicGas { + if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) { t.Error("expected", ErrIntrinsicGas, "got", err) } diff --git a/core/types/block.go b/core/types/block.go index 091be50aab..cb26320c94 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -22,11 +22,13 @@ import ( "io" "math/big" "reflect" + "sync" "sync/atomic" "time" "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/common/hexutil" + "github.com/celo-org/celo-blockchain/crypto" blscrypto "github.com/celo-org/celo-blockchain/crypto/bls" "github.com/celo-org/celo-blockchain/rlp" "golang.org/x/crypto/sha3" @@ -97,10 +99,19 @@ func (h *Header) SanityCheck() error { return nil } +// hasherPool holds LegacyKeccak hashers. +var hasherPool = sync.Pool{ + New: func() interface{} { + return sha3.NewLegacyKeccak256() + }, +} + func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) return h } diff --git a/core/types/block_test.go b/core/types/block_test.go index 47dbb0e0a6..fef3b8dd1d 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -18,10 +18,14 @@ package types import ( "bytes" + "math/big" "reflect" "testing" "github.com/celo-org/celo-blockchain/common" + "github.com/celo-org/celo-blockchain/common/math" + "github.com/celo-org/celo-blockchain/crypto" + "github.com/celo-org/celo-blockchain/params" "github.com/celo-org/celo-blockchain/rlp" ) @@ -56,3 +60,54 @@ func TestBlockEncoding(t *testing.T) { t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) } } + +var benchBuffer = bytes.NewBuffer(make([]byte, 0, 32000)) + +func BenchmarkEncodeBlock(b *testing.B) { + block := makeBenchBlock() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchBuffer.Reset() + if err := rlp.Encode(benchBuffer, block); err != nil { + b.Fatal(err) + } + } +} + +func makeBenchBlock() *Block { + var ( + key, _ = crypto.GenerateKey() + txs = make([]*Transaction, 70) + receipts = make([]*Receipt, len(txs)) + signer = NewEIP155Signer(params.TestChainConfig.ChainID) + uncles = make([]*Header, 3) + ) + header := &Header{ + Number: math.BigPow(2, 9), + GasUsed: 1476322, + Time: 9876543, + Extra: []byte("coolest block on chain"), + } + for i := range txs { + amount := math.BigPow(2, int64(i)) + price := big.NewInt(300000) + data := make([]byte, 100) + tx := NewTransaction(uint64(i), common.Address{}, amount, 123457, price, nil, nil, nil, data) + signedTx, err := SignTx(tx, signer, key) + if err != nil { + panic(err) + } + txs[i] = signedTx + receipts[i] = NewReceipt(make([]byte, 32), false, tx.Gas()) + } + for i := range uncles { + uncles[i] = &Header{ + Number: math.BigPow(2, 9), + GasUsed: 1476322, + Time: 9876543, + Extra: []byte("benchmark uncle"), + } + } + return NewBlock(header, txs, receipts, nil) +} diff --git a/core/types/transaction.go b/core/types/transaction.go index 846fbc1748..16b0b30cce 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -277,13 +277,20 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return nil } -func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } -func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } +func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } +func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } +func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } +func (tx *Transaction) GasPriceCmp(other *Transaction) int { + return tx.data.Price.Cmp(other.data.Price) +} +func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { + return tx.data.Price.Cmp(other) +} func (tx *Transaction) FeeCurrency() *common.Address { return tx.data.FeeCurrency } func (tx *Transaction) GatewayFeeRecipient() *common.Address { return tx.data.GatewayFeeRecipient } func (tx *Transaction) GatewayFee() *big.Int { return tx.data.GatewayFee } func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } +func (tx *Transaction) ValueU64() uint64 { return tx.data.Amount.Uint64() } func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) CheckNonce() bool { return true } func (tx *Transaction) EthCompatible() bool { return tx.data.EthCompatible } diff --git a/core/vm/common.go b/core/vm/common.go index 4f9eca8733..4c49bd6a11 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -17,15 +17,14 @@ package vm import ( - "math/big" - "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/common/math" + "github.com/holiman/uint256" ) // calcMemSize64 calculates the required memory size, and returns // the size and whether the result overflowed uint64 -func calcMemSize64(off, l *big.Int) (uint64, bool) { +func calcMemSize64(off, l *uint256.Int) (uint64, bool) { if !l.IsUint64() { return 0, true } @@ -35,16 +34,16 @@ func calcMemSize64(off, l *big.Int) (uint64, bool) { // calcMemSize64WithUint calculates the required memory size, and returns // the size and whether the result overflowed uint64 // Identical to calcMemSize64, but length is a uint64 -func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) { +func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) { // if length is zero, memsize is always zero, regardless of offset if length64 == 0 { return 0, false } // Check that offset doesn't overflow - if !off.IsUint64() { + offset64, overflow := off.Uint64WithOverflow() + if overflow { return 0, true } - offset64 := off.Uint64() val := offset64 + length64 // if value < either of it's parts, then it overflowed return val, val < offset64 @@ -64,22 +63,6 @@ func getData(data []byte, start uint64, size uint64) []byte { return common.RightPadBytes(data[start:end], int(size)) } -// getDataBig returns a slice from the data based on the start and size and pads -// up to size with zero's. This function is overflow safe. -func getDataBig(data []byte, start *big.Int, size *big.Int) []byte { - dlen := big.NewInt(int64(len(data))) - - s := math.BigMin(start, dlen) - e := math.BigMin(new(big.Int).Add(s, size), dlen) - return common.RightPadBytes(data[s.Uint64():e.Uint64()], int(size.Uint64())) -} - -// bigUint64 returns the integer casted to a uint64 and returns whether it -// overflowed in the process. -func bigUint64(v *big.Int) (uint64, bool) { - return v.Uint64(), !v.IsUint64() -} - // toWordSize returns the ceiled word size required for memory expansion. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { diff --git a/core/vm/contract.go b/core/vm/contract.go index b0eab8b2e5..91a0573a5f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -21,6 +21,7 @@ import ( "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/log" + "github.com/holiman/uint256" ) // ContractRef is a reference to the contract's backing object @@ -82,11 +83,11 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin return c } -func (c *Contract) validJumpdest(dest *big.Int) bool { - udest := dest.Uint64() - // PC cannot go beyond len(code) and certainly can't be bigger than 63 bits. +func (c *Contract) validJumpdest(dest *uint256.Int) bool { + udest, overflow := dest.Uint64WithOverflow() + // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // Don't bother checking for JUMPDEST in that case. - if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) { + if overflow || udest >= uint64(len(c.Code)) { return false } // Only JUMPDESTs allowed for destinations diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index cf2401fc2d..c8182f4754 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -25,6 +25,7 @@ import ( "math/big" "reflect" "testing" + "time" blscrypto "github.com/celo-org/celo-blockchain/crypto/bls" @@ -999,6 +1000,8 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { ) bench.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(bench *testing.B) { + bench.ReportAllocs() + start := time.Now().Nanosecond() bench.ResetTimer() for i := 0; i < bench.N; i++ { contract.Gas = reqGas @@ -1006,6 +1009,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { res, err = RunPrecompiledContract(p, data, contract, mockEVM) } bench.StopTimer() + elapsed := float64(time.Now().Nanosecond() - start) + if elapsed < 1 { + elapsed = 1 + } + gasUsed := reqGas * uint64(bench.N) + bench.ReportMetric(float64(reqGas), "gas/op") + bench.ReportMetric(float64(gasUsed*1000)/elapsed, "mgas/s") //Check if it is correct if err != nil { bench.Error(err) @@ -1393,3 +1403,42 @@ func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJSONFail("fail-bl func TestPrecompiledBLS12381PairingFail(t *testing.T) { testJSONFail("fail-blsPairing", "ec", t) } func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJSONFail("fail-blsMapG1", "eb", t) } func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJSONFail("fail-blsMapG2", "ea", t) } + +// BenchmarkPrecompiledBLS12381G1MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas. +func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) { + task := "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be1" + + "0000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + input := task + for i := 0; i < 4787; i++ { + input = input + task + } + testcase := precompiledTest{ + input: input, + expected: "0000000000000000000000000000000005a6310ea6f2a598023ae48819afc292b4dfcb40aabad24a0c2cb6c19769465691859eeb2a764342a810c5038d700f18000000000000000000000000000000001268ac944437d15923dc0aec00daa9250252e43e4b35ec7a19d01f0d6cd27f6e139d80dae16ba1c79cc7f57055a93ff5", + name: "WorstCaseG1", + noBenchmark: false, + } + benchmarkPrecompiled("0c", testcase, b) +} + +// BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas. +func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) { + task := "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f" + + "000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b99" + + "00000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b" + + "000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + input := task + for i := 0; i < 1040; i++ { + input = input + task + } + + testcase := precompiledTest{ + input: input, + expected: "0000000000000000000000000000000018f5ea0c8b086095cfe23f6bb1d90d45de929292006dba8cdedd6d3203af3c6bbfd592e93ecb2b2c81004961fdcbb46c00000000000000000000000000000000076873199175664f1b6493a43c02234f49dc66f077d3007823e0343ad92e30bd7dc209013435ca9f197aca44d88e9dac000000000000000000000000000000000e6f07f4b23b511eac1e2682a0fc224c15d80e122a3e222d00a41fab15eba645a700b9ae84f331ae4ed873678e2e6c9b000000000000000000000000000000000bcb4849e460612aaed79617255fd30c03f51cf03d2ed4163ca810c13e1954b1e8663157b957a601829bb272a4e6c7b8", + name: "WorstCaseG2", + noBenchmark: false, + } + benchmarkPrecompiled("0f", testcase, b) +} diff --git a/core/vm/eips.go b/core/vm/eips.go index 735fe1a025..d501e00d1a 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,28 +18,44 @@ package vm import ( "fmt" + "sort" + // "github.com/celo-org/celo-blockchain/params" + "github.com/holiman/uint256" ) +var activators = map[int]func(*JumpTable){ + 2200: enable2200, + 1884: enable1884, + 1344: enable1344, + 2315: enable2315, +} + // EnableEIP enables the given EIP on the config. // This operation writes in-place, and callers need to ensure that the globally // defined jump tables are not polluted. func EnableEIP(eipNum int, jt *JumpTable) error { - switch eipNum { - case 2200: - enable2200(jt) - case 1884: - enable1884(jt) - case 1344: - enable1344(jt) - case 2315: - enable2315(jt) - default: + enablerFn, ok := activators[eipNum] + if !ok { return fmt.Errorf("undefined eip %d", eipNum) } + enablerFn(jt) return nil } +func ValidEip(eipNum int) bool { + _, ok := activators[eipNum] + return ok +} +func ActivateableEips() []string { + var nums []string + for k := range activators { + nums = append(nums, fmt.Sprintf("%d", k)) + } + sort.Strings(nums) + return nums +} + // enable1884 applies EIP-1884 to the given jump table: // - Increase cost of BALANCE to 700 // - Increase cost of EXTCODEHASH to 700 @@ -64,7 +80,7 @@ func enable1884(jt *JumpTable) { } func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) + balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) callContext.stack.push(balance) return nil, nil } @@ -84,7 +100,7 @@ func enable1344(jt *JumpTable) { // opChainID implements CHAINID opcode func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - chainId := interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID) + chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) callContext.stack.push(chainId) return nil, nil } diff --git a/core/vm/evm.go b/core/vm/evm.go index 33b576e6c7..636081d918 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -397,7 +397,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, bigZero) + evm.StateDB.AddBalance(addr, big.NewInt(0)) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally diff --git a/core/vm/gas.go b/core/vm/gas.go index bda326cdc7..5cf1d852d2 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -17,7 +17,7 @@ package vm import ( - "math/big" + "github.com/holiman/uint256" ) // Gas costs @@ -34,7 +34,7 @@ const ( // // The cost of gas was changed during the homestead price change HF. // As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64. -func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint64, error) { +func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) { if isEip150 { availableGas = availableGas - base gas := availableGas - availableGas/64 diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f2695dc165..d9ef7efed5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -70,7 +70,7 @@ func memoryCopierGas(stackpos int) gasFunc { return 0, err } // And gas for copying data, charged per word at param.CopyGas - words, overflow := bigUint64(stack.Back(stackpos)) + words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } @@ -96,7 +96,7 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -131,11 +131,11 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 2.2.2. If original value equals new value (this storage slot is reset) // 2.2.2.1. If original value is 0, add 19800 gas to refund counter. // 2.2.2.2. Otherwise, add 4800 gas to refund counter. - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -183,14 +183,14 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) ) - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SstoreNoopGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreInitGasEIP2200, nil @@ -219,7 +219,7 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m func makeGasLog(n uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - requestedSize, overflow := bigUint64(stack.Back(1)) + requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } @@ -252,7 +252,7 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(1)) + wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } @@ -286,7 +286,7 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(2)) + wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } @@ -328,8 +328,8 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 - transfersValue = stack.Back(2).Sign() != 0 - address = common.BigToAddress(stack.Back(1)) + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) ) if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { @@ -422,7 +422,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 - var address = common.BigToAddress(stack.Back(0)) + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsEIP158 { // if empty and transfers value diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b409a7270d..8f1fd6af27 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,302 +17,170 @@ package vm import ( - "math/big" - "github.com/celo-org/celo-blockchain/common" - "github.com/celo-org/celo-blockchain/common/math" "github.com/celo-org/celo-blockchain/core/types" "github.com/celo-org/celo-blockchain/params" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) -var ( - bigZero = new(big.Int) - tt255 = math.BigPow(2, 255) -) - func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - math.U256(y.Add(x, y)) - - interpreter.intPool.putOne(x) + y.Add(&x, y) return nil, nil } func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - math.U256(y.Sub(x, y)) - - interpreter.intPool.putOne(x) + y.Sub(&x, y) return nil, nil } func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.pop() - callContext.stack.push(math.U256(x.Mul(x, y))) - - interpreter.intPool.putOne(y) - + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mul(&x, y) return nil, nil } func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - if y.Sign() != 0 { - math.U256(y.Div(x, y)) - } else { - y.SetUint64(0) - } - interpreter.intPool.putOne(x) + y.Div(&x, y) return nil, nil } func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 || x.Sign() == 0 { - callContext.stack.push(res) - } else { - if x.Sign() != y.Sign() { - res.Div(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Div(x.Abs(x), y.Abs(y)) - } - callContext.stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SDiv(&x, y) return nil, nil } func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.pop() - if y.Sign() == 0 { - callContext.stack.push(x.SetUint64(0)) - } else { - callContext.stack.push(math.U256(x.Mod(x, y))) - } - interpreter.intPool.putOne(y) + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mod(&x, y) return nil, nil } func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 { - callContext.stack.push(res) - } else { - if x.Sign() < 0 { - res.Mod(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Mod(x.Abs(x), y.Abs(y)) - } - callContext.stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SMod(&x, y) return nil, nil } func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - base, exponent := callContext.stack.pop(), callContext.stack.pop() - // some shortcuts - cmpToOne := exponent.Cmp(big1) - if cmpToOne < 0 { // Exponent is zero - // x ^ 0 == 1 - callContext.stack.push(base.SetUint64(1)) - } else if base.Sign() == 0 { - // 0 ^ y, if y != 0, == 0 - callContext.stack.push(base.SetUint64(0)) - } else if cmpToOne == 0 { // Exponent is one - // x ^ 1 == x - callContext.stack.push(base) - } else { - callContext.stack.push(math.Exp(base, exponent)) - interpreter.intPool.putOne(base) - } - interpreter.intPool.putOne(exponent) + base, exponent := callContext.stack.pop(), callContext.stack.peek() + exponent.Exp(&base, exponent) return nil, nil } func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - back := callContext.stack.pop() - if back.Cmp(big.NewInt(31)) < 0 { - bit := uint(back.Uint64()*8 + 7) - num := callContext.stack.pop() - mask := back.Lsh(common.Big1, bit) - mask.Sub(mask, common.Big1) - if num.Bit(int(bit)) > 0 { - num.Or(num, mask.Not(mask)) - } else { - num.And(num, mask) - } - - callContext.stack.push(math.U256(num)) - } - - interpreter.intPool.putOne(back) + back, num := callContext.stack.pop(), callContext.stack.peek() + num.ExtendSign(num, &back) return nil, nil } func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x := callContext.stack.peek() - math.U256(x.Not(x)) + x.Not(x) return nil, nil } func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - if x.Cmp(y) < 0 { - y.SetUint64(1) + if x.Lt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.putOne(x) return nil, nil } func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - if x.Cmp(y) > 0 { - y.SetUint64(1) + if x.Gt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.putOne(x) return nil, nil } func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(1) - - case xSign < 0 && ySign >= 0: - y.SetUint64(0) - - default: - if x.Cmp(y) < 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } + if x.Slt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.putOne(x) return nil, nil } func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(0) - - case xSign < 0 && ySign >= 0: - y.SetUint64(1) - - default: - if x.Cmp(y) > 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } + if x.Sgt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.putOne(x) return nil, nil } func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - if x.Cmp(y) == 0 { - y.SetUint64(1) + if x.Eq(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.putOne(x) return nil, nil } func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x := callContext.stack.peek() - if x.Sign() > 0 { - x.SetUint64(0) + if x.IsZero() { + x.SetOne() } else { - x.SetUint64(1) + x.Clear() } return nil, nil } func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.pop() - callContext.stack.push(x.And(x, y)) - - interpreter.intPool.putOne(y) + x, y := callContext.stack.pop(), callContext.stack.peek() + y.And(&x, y) return nil, nil } func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - y.Or(x, y) - - interpreter.intPool.putOne(x) + y.Or(&x, y) return nil, nil } func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { x, y := callContext.stack.pop(), callContext.stack.peek() - y.Xor(x, y) - - interpreter.intPool.putOne(x) + y.Xor(&x, y) return nil, nil } func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { th, val := callContext.stack.pop(), callContext.stack.peek() - if th.Cmp(common.Big32) < 0 { - b := math.Byte(val, 32, int(th.Int64())) - val.SetUint64(uint64(b)) - } else { - val.SetUint64(0) - } - interpreter.intPool.putOne(th) + val.Byte(&th) return nil, nil } func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - if z.Cmp(bigZero) > 0 { - x.Add(x, y) - x.Mod(x, z) - callContext.stack.push(math.U256(x)) + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + if z.IsZero() { + z.Clear() } else { - callContext.stack.push(x.SetUint64(0)) + z.AddMod(&x, &y, z) } - interpreter.intPool.put(y, z) return nil, nil } func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - if z.Cmp(bigZero) > 0 { - x.Mul(x, y) - x.Mod(x, z) - callContext.stack.push(math.U256(x)) - } else { - callContext.stack.push(x.SetUint64(0)) - } - interpreter.intPool.put(y, z) + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + z.MulMod(&x, &y, z) return nil, nil } @@ -321,16 +189,12 @@ func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] // and pushes on the stack arg2 shifted to the left by arg1 number of bits. func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek()) - defer interpreter.intPool.putOne(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - value.SetBytes(math.U256(new(big.Int).Lsh(value, n)).Bytes()) - return nil, nil } @@ -339,16 +203,12 @@ func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek()) - defer interpreter.intPool.putOne(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Rsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - math.U256(value.Rsh(value, n)) - return nil, nil } @@ -356,29 +216,24 @@ func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - // Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one - shift, value := math.U256(callContext.stack.pop()), math.S256(callContext.stack.pop()) - defer interpreter.intPool.putOne(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.GtUint64(256) { if value.Sign() >= 0 { - value.SetUint64(0) + value.Clear() } else { - value.SetInt64(-1) + // Max negative shift: all bits set + value.SetAllOne() } - callContext.stack.push(math.U256(value)) return nil, nil } n := uint(shift.Uint64()) - value.Rsh(value, n) - callContext.stack.push(math.U256(value)) - + value.SRsh(value, n) return nil, nil } func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - offset, size := callContext.stack.pop(), callContext.stack.pop() - data := callContext.memory.GetPtr(offset.Int64(), size.Int64()) + offset, size := callContext.stack.pop(), callContext.stack.peek() + data := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) if interpreter.hasher == nil { interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) @@ -392,45 +247,50 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by if evm.vmConfig.EnablePreimageRecording { evm.StateDB.AddPreimage(interpreter.hasherBuf, data) } - callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) - interpreter.intPool.put(offset, size) + size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } - func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Address().Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Address().Bytes())) return nil, nil } func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - slot.Set(interpreter.evm.StateDB.GetBalance(common.BigToAddress(slot))) + address := common.Address(slot.Bytes20()) + slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } - func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Caller().Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Caller().Bytes())) return nil, nil } func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().Set(callContext.contract.value)) + v, _ := uint256.FromBig(callContext.contract.value) + callContext.stack.push(v) return nil, nil } func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetBytes(getDataBig(callContext.contract.Input, callContext.stack.pop(), big32))) + x := callContext.stack.peek() + if offset, overflow := x.Uint64WithOverflow(); !overflow { + data := getData(callContext.contract.Input, offset, 32) + x.SetBytes(data) + } else { + x.Clear() + } return nil, nil } func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Input)))) + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(callContext.contract.Input)))) return nil, nil } @@ -440,14 +300,20 @@ func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCt dataOffset = callContext.stack.pop() length = callContext.stack.pop() ) - callContext.memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(callContext.contract.Input, dataOffset, length)) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + dataOffset64 = 0xffffffffffffffff + } + // These values are checked for overflow during gas cost calculation + memOffset64 := memOffset.Uint64() + length64 := length.Uint64() + callContext.memory.Set(memOffset64, length64, getData(callContext.contract.Input, dataOffset64, length64)) - interpreter.intPool.put(memOffset, dataOffset, length) return nil, nil } func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData)))) + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) return nil, nil } @@ -456,30 +322,33 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call memOffset = callContext.stack.pop() dataOffset = callContext.stack.pop() length = callContext.stack.pop() - - end = interpreter.intPool.get().Add(dataOffset, length) ) - defer interpreter.intPool.put(memOffset, dataOffset, length, end) - if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() { + offset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { return nil, ErrReturnDataOutOfBounds } - callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) - + // we can reuse dataOffset now (aliasing it for clarity) + var end = dataOffset + end.Add(&dataOffset, &length) + end64, overflow := end.Uint64WithOverflow() + if overflow || uint64(len(interpreter.returnData)) < end64 { + return nil, ErrReturnDataOutOfBounds + } + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil } func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.BigToAddress(slot)))) - + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.Address(slot.Bytes20())))) return nil, nil } func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - l := interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Code))) + l := new(uint256.Int) + l.SetUint64(uint64(len(callContext.contract.Code))) callContext.stack.push(l) - return nil, nil } @@ -489,24 +358,32 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( codeOffset = callContext.stack.pop() length = callContext.stack.pop() ) - codeCopy := getDataBig(callContext.contract.Code, codeOffset, length) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + codeCopy := getData(callContext.contract.Code, uint64CodeOffset, length.Uint64()) callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - addr = common.BigToAddress(callContext.stack.pop()) - memOffset = callContext.stack.pop() - codeOffset = callContext.stack.pop() - length = callContext.stack.pop() + stack = callContext.stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() ) - codeCopy := getDataBig(interpreter.evm.StateDB.GetCode(addr), codeOffset, length) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + addr := common.Address(a.Bytes20()) + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } @@ -538,9 +415,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx // this account should be regarded as a non-existent account and zero should be returned. func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - address := common.BigToAddress(slot) + address := common.Address(slot.Bytes20()) if interpreter.evm.StateDB.Empty(address) { - slot.SetUint64(0) + slot.Clear() } else { slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } @@ -548,46 +425,58 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx } func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice)) + v, _ := uint256.FromBig(interpreter.evm.GasPrice) + callContext.stack.push(v) return nil, nil } func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - num := callContext.stack.pop() - - n := interpreter.intPool.get().Sub(interpreter.evm.BlockNumber, common.Big257) - if num.Cmp(n) > 0 && num.Cmp(interpreter.evm.BlockNumber) < 0 { - callContext.stack.push(interpreter.evm.GetHash(num.Uint64()).Big()) + num := callContext.stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + var upper, lower uint64 + upper = interpreter.evm.BlockNumber.Uint64() + if upper < 257 { + lower = 0 } else { - callContext.stack.push(interpreter.intPool.getZero()) + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + num.SetBytes(interpreter.evm.GetHash(num64).Bytes()) + } else { + num.Clear() } - interpreter.intPool.put(num, n) return nil, nil } func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Coinbase.Bytes())) return nil, nil } func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time))) + v, _ := uint256.FromBig(interpreter.evm.Time) + callContext.stack.push(v) return nil, nil } func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber))) + v, _ := uint256.FromBig(interpreter.evm.BlockNumber) + callContext.stack.push(v) return nil, nil } func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - interpreter.intPool.putOne(callContext.stack.pop()) + callContext.stack.pop() return nil, nil } func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { v := callContext.stack.peek() - offset := v.Int64() + offset := int64(v.Uint64()) v.SetBytes(callContext.memory.GetPtr(offset, 32)) return nil, nil } @@ -595,58 +484,51 @@ func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]b func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // pop value of the stack mStart, val := callContext.stack.pop(), callContext.stack.pop() - callContext.memory.Set32(mStart.Uint64(), val) - - interpreter.intPool.put(mStart, val) + callContext.memory.Set32(mStart.Uint64(), &val) return nil, nil } func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - off, val := callContext.stack.pop().Int64(), callContext.stack.pop().Int64() - callContext.memory.store[off] = byte(val & 0xff) - + off, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.store[off.Uint64()] = byte(val.Uint64()) return nil, nil } func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { loc := callContext.stack.peek() - val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), common.BigToHash(loc)) + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil } func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - loc := common.BigToHash(callContext.stack.pop()) + loc := callContext.stack.pop() val := callContext.stack.pop() - interpreter.evm.StateDB.SetState(callContext.contract.Address(), loc, common.BigToHash(val)) - - interpreter.intPool.putOne(val) + interpreter.evm.StateDB.SetState(callContext.contract.Address(), + common.Hash(loc.Bytes32()), common.Hash(val.Bytes32())) return nil, nil } func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { pos := callContext.stack.pop() - if !callContext.contract.validJumpdest(pos) { + if !callContext.contract.validJumpdest(&pos) { return nil, ErrInvalidJump } *pc = pos.Uint64() - - interpreter.intPool.putOne(pos) return nil, nil } func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { pos, cond := callContext.stack.pop(), callContext.stack.pop() - if cond.Sign() != 0 { - if !callContext.contract.validJumpdest(pos) { + if !cond.IsZero() { + if !callContext.contract.validJumpdest(&pos) { return nil, ErrInvalidJump } *pc = pos.Uint64() } else { *pc++ } - - interpreter.intPool.put(pos, cond) return nil, nil } @@ -672,7 +554,6 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ } callContext.rstack.push(*pc) *pc = posU64 + 1 - interpreter.intPool.put(pos) return nil, nil } @@ -688,17 +569,17 @@ func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) } func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetUint64(*pc)) + callContext.stack.push(new(uint256.Int).SetUint64(*pc)) return nil, nil } func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetInt64(int64(callContext.memory.Len()))) + callContext.stack.push(new(uint256.Int).SetUint64(uint64(callContext.memory.Len()))) return nil, nil } func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(interpreter.intPool.get().SetUint64(callContext.contract.Gas)) + callContext.stack.push(new(uint256.Int).SetUint64(callContext.contract.Gas)) return nil, nil } @@ -706,28 +587,30 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] var ( value = callContext.stack.pop() offset, size = callContext.stack.pop(), callContext.stack.pop() - input = callContext.memory.GetCopy(offset.Int64(), size.Int64()) + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = callContext.contract.Gas ) if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } + // reuse size int for stackvalue + stackvalue := size callContext.contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value) + res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig()) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { - callContext.stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { - callContext.stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } + callContext.stack.push(&stackvalue) callContext.contract.Gas += returnGas - interpreter.intPool.put(value, offset, size) if suberr == ErrExecutionReverted { return res, nil @@ -740,22 +623,25 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ endowment = callContext.stack.pop() offset, size = callContext.stack.pop(), callContext.stack.pop() salt = callContext.stack.pop() - input = callContext.memory.GetCopy(offset.Int64(), size.Int64()) + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = callContext.contract.Gas ) // Apply EIP150 gas -= gas / 64 callContext.contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, endowment, salt) + // reuse size int for stackvalue + stackvalue := size + res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, + endowment.ToBig(), salt.ToBig()) // Push item on the stack based on the returned error. if suberr != nil { - callContext.stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } + callContext.stack.push(&stackvalue) callContext.contract.Gas += returnGas - interpreter.intPool.put(endowment, offset, size, salt) if suberr == ErrExecutionReverted { return res, nil @@ -764,126 +650,130 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ } func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. - interpreter.intPool.putOne(callContext.stack.pop()) + // We can use this as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. - args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + if !value.IsZero() { gas += params.CallStipend } - ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value) + ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value.ToBig()) if err != nil { - callContext.stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.putOne(callContext.stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + if !value.IsZero() { gas += params.CallStipend } - ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value) + ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value.ToBig()) if err != nil { - callContext.stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.putOne(callContext.stack.pop()) + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - toAddr := common.BigToAddress(addr) + addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas) if err != nil { - callContext.stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.putOne(callContext.stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() - toAddr := common.BigToAddress(addr) + addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas) if err != nil { - callContext.stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - callContext.stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { offset, size := callContext.stack.pop(), callContext.stack.pop() - ret := callContext.memory.GetPtr(offset.Int64(), size.Int64()) + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { offset, size := callContext.stack.pop(), callContext.stack.pop() - ret := callContext.memory.GetPtr(offset.Int64(), size.Int64()) + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } @@ -892,9 +782,9 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by } func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + beneficiary := callContext.stack.pop() balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) - interpreter.evm.StateDB.AddBalance(common.BigToAddress(callContext.stack.pop()), balance) - + interpreter.evm.StateDB.AddBalance(common.Address(beneficiary.Bytes20()), balance) interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } @@ -905,12 +795,14 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ func makeLog(size int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { topics := make([]common.Hash, size) - mStart, mSize := callContext.stack.pop(), callContext.stack.pop() + stack := callContext.stack + mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { - topics[i] = common.BigToHash(callContext.stack.pop()) + addr := stack.pop() + topics[i] = common.Hash(addr.Bytes32()) } - d := callContext.memory.GetCopy(mStart.Int64(), mSize.Int64()) + d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) interpreter.evm.StateDB.AddLog(&types.Log{ Address: callContext.contract.Address(), Topics: topics, @@ -920,7 +812,6 @@ func makeLog(size int) executionFunc { BlockNumber: interpreter.evm.BlockNumber.Uint64(), }) - interpreter.intPool.put(mStart, mSize) return nil, nil } } @@ -929,13 +820,13 @@ func makeLog(size int) executionFunc { func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( codeLen = uint64(len(callContext.contract.Code)) - integer = interpreter.intPool.get() + integer = new(uint256.Int) ) *pc += 1 if *pc < codeLen { callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc]))) } else { - callContext.stack.push(integer.SetUint64(0)) + callContext.stack.push(integer.Clear()) } return nil, nil } @@ -955,8 +846,9 @@ func makePush(size uint64, pushByteSize int) executionFunc { endMin = startMin + pushByteSize } - integer := interpreter.intPool.get() - callContext.stack.push(integer.SetBytes(common.RightPadBytes(callContext.contract.Code[startMin:endMin], pushByteSize))) + integer := new(uint256.Int) + callContext.stack.push(integer.SetBytes(common.RightPadBytes( + callContext.contract.Code[startMin:endMin], pushByteSize))) *pc += size return nil, nil @@ -966,7 +858,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { // make dup instruction function func makeDup(size int64) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.dup(interpreter.intPool, int(size)) + callContext.stack.dup(int(size)) return nil, nil } } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index d433d3d9da..dff40fc4a5 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -21,12 +21,12 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/big" "testing" "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/crypto" "github.com/celo-org/celo-blockchain/params" + "github.com/holiman/uint256" ) type TwoOperandTestcase struct { @@ -98,42 +98,23 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu pc = uint64(0) evmInterpreter = env.interpreter.(*EVMInterpreter) ) - // Stuff a couple of nonzero bigints into pool, to ensure that ops do not rely on pooled integers to be zero - evmInterpreter.intPool = poolOfIntPools.get() - evmInterpreter.intPool.put(big.NewInt(-1337)) - evmInterpreter.intPool.put(big.NewInt(-1337)) - evmInterpreter.intPool.put(big.NewInt(-1337)) for i, test := range tests { - x := new(big.Int).SetBytes(common.Hex2Bytes(test.X)) - y := new(big.Int).SetBytes(common.Hex2Bytes(test.Y)) - expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected)) + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) opFn(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) + } actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %v %d, %v(%x, %x): expected %x, got %x", name, i, name, x, y, expected, actual) } - // Check pool usage - // 1.pool is not allowed to contain anything on the stack - // 2.pool is not allowed to contain the same pointers twice - if evmInterpreter.intPool.pool.len() > 0 { - - poolvals := make(map[*big.Int]struct{}) - poolvals[actual] = struct{}{} - - for evmInterpreter.intPool.pool.len() > 0 { - key := evmInterpreter.intPool.get() - if _, exist := poolvals[key]; exist { - t.Errorf("Testcase %v %d, pool contains double-entry", name, i) - } - poolvals[key] = struct{}{} - } - } } - poolOfIntPools.put(evmInterpreter.intPool) } func TestByteOp(t *testing.T) { @@ -209,6 +190,44 @@ func TestSAR(t *testing.T) { testTwoOperandOp(t, tests, opSAR, "sar") } +func TestAddMod(t *testing.T) { + var ( + env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + evmInterpreter = NewEVMInterpreter(env, &env.vmConfig) + pc = uint64(0) + ) + tests := []struct { + x string + y string + z string + expected string + }{ + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + }, + } + // x + y = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + // in 256 bit repr, fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + + for i, test := range tests { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y)) + z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected)) + stack.push(z) + stack.push(y) + stack.push(x) + opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil, nil}) + actual := stack.pop() + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) + } + } +} + // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( @@ -217,11 +236,10 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas pc = uint64(0) interpreter = env.interpreter.(*EVMInterpreter) ) - interpreter.intPool = poolOfIntPools.get() result := make([]TwoOperandTestcase, len(args)) for i, param := range args { - x := new(big.Int).SetBytes(common.Hex2Bytes(param.x)) - y := new(big.Int).SetBytes(common.Hex2Bytes(param.y)) + x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) opFn(&pc, interpreter, &callCtx{nil, stack, rstack, nil}) @@ -269,7 +287,6 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() // convert args byteArgs := make([][]byte, len(args)) for i, arg := range args { @@ -279,13 +296,13 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { bench.ResetTimer() for i := 0; i < bench.N; i++ { for _, arg := range byteArgs { - a := new(big.Int).SetBytes(arg) + a := new(uint256.Int) + a.SetBytes(arg) stack.push(a) } op(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) stack.pop() } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpAdd64(b *testing.B) { @@ -505,21 +522,19 @@ func TestOpMstore(t *testing.T) { ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(64) pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" - stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0)) + stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } - stack.pushN(big.NewInt(0x1), big.NewInt(0)) + stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int)) opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpMstore(bench *testing.B) { @@ -531,18 +546,16 @@ func BenchmarkOpMstore(bench *testing.B) { ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(64) pc := uint64(0) - memStart := big.NewInt(0) - value := big.NewInt(0x1337) + memStart := new(uint256.Int) + value := new(uint256.Int).SetUint64(0x1337) bench.ResetTimer() for i := 0; i < bench.N; i++ { - stack.pushN(value, memStart) + stack.pushN(*value, *memStart) opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpSHA3(bench *testing.B) { @@ -553,17 +566,15 @@ func BenchmarkOpSHA3(bench *testing.B) { evmInterpreter = NewEVMInterpreter(env, &env.vmConfig) ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(32) pc := uint64(0) - start := big.NewInt(0) + start := uint256.NewInt() bench.ResetTimer() for i := 0; i < bench.N; i++ { - stack.pushN(big.NewInt(32), start) + stack.pushN(*uint256.NewInt().SetUint64(32), *start) opSha3(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) } - poolOfIntPools.put(evmInterpreter.intPool) } func TestCreate2Addreses(t *testing.T) { @@ -637,6 +648,5 @@ func TestCreate2Addreses(t *testing.T) { if !bytes.Equal(expected.Bytes(), address.Bytes()) { t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String()) } - } } diff --git a/core/vm/int_pool_verifier.go b/core/vm/int_pool_verifier.go deleted file mode 100644 index 82fbfed699..0000000000 --- a/core/vm/int_pool_verifier.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build VERIFY_EVM_INTEGER_POOL - -package vm - -import "fmt" - -const verifyPool = true - -func verifyIntegerPool(ip *intPool) { - for i, item := range ip.pool.data { - if item.Cmp(checkVal) != 0 { - panic(fmt.Sprintf("%d'th item failed aggressive pool check. Value was modified", i)) - } - } -} diff --git a/core/vm/int_pool_verifier_empty.go b/core/vm/int_pool_verifier_empty.go deleted file mode 100644 index a5f1dc02b7..0000000000 --- a/core/vm/int_pool_verifier_empty.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !VERIFY_EVM_INTEGER_POOL - -package vm - -const verifyPool = false - -func verifyIntegerPool(ip *intPool) {} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index cb4265f1d2..41bae0bad3 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -84,8 +84,6 @@ type EVMInterpreter struct { evm *EVM cfg *Config - intPool *intPool - hasher keccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes @@ -139,13 +137,6 @@ func NewEVMInterpreter(evm *EVM, cfg *Config) *EVMInterpreter { // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { - if in.intPool == nil { - in.intPool = poolOfIntPools.get() - defer func() { - poolOfIntPools.put(in.intPool) - in.intPool = nil - }() - } // Increment the call depth which is restricted to 1024 in.evm.depth++ @@ -191,9 +182,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( ) contract.Input = input - // Reclaim the stack as an int pool when the execution stops - defer func() { in.intPool.put(stack.data...) }() - if in.cfg.Debug { defer func() { if err != nil { @@ -291,11 +279,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // execute the operation res, err = operation.execute(&pc, in, callContext) - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - verifyIntegerPool(in.intPool) - } // if the operation clears the return data (e.g. it has returning data) // set the last return to the result of the operation. if operation.returns { diff --git a/core/vm/intpool.go b/core/vm/intpool.go deleted file mode 100644 index eed074b073..0000000000 --- a/core/vm/intpool.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "math/big" - "sync" -) - -var checkVal = big.NewInt(-42) - -const poolLimit = 256 - -// intPool is a pool of big integers that -// can be reused for all big.Int operations. -type intPool struct { - pool *Stack -} - -func newIntPool() *intPool { - return &intPool{pool: newstack()} -} - -// get retrieves a big int from the pool, allocating one if the pool is empty. -// Note, the returned int's value is arbitrary and will not be zeroed! -func (p *intPool) get() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop() - } - return new(big.Int) -} - -// getZero retrieves a big int from the pool, setting it to zero or allocating -// a new one if the pool is empty. -func (p *intPool) getZero() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop().SetUint64(0) - } - return new(big.Int) -} - -// putOne returns an allocated big int to the pool to be later reused by get calls. -// Note, the values as saved as is; neither put nor get zeroes the ints out! -// As opposed to 'put' with variadic args, this method becomes inlined by the -// go compiler -func (p *intPool) putOne(i *big.Int) { - if len(p.pool.data) > poolLimit { - return - } - p.pool.push(i) -} - -// put returns an allocated big int to the pool to be later reused by get calls. -// Note, the values as saved as is; neither put nor get zeroes the ints out! -func (p *intPool) put(is ...*big.Int) { - if len(p.pool.data) > poolLimit { - return - } - for _, i := range is { - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - i.Set(checkVal) - } - p.pool.push(i) - } -} - -// The intPool pool's default capacity -const poolDefaultCap = 25 - -// intPoolPool manages a pool of intPools. -type intPoolPool struct { - pools []*intPool - lock sync.Mutex -} - -var poolOfIntPools = &intPoolPool{ - pools: make([]*intPool, 0, poolDefaultCap), -} - -// get is looking for an available pool to return. -func (ipp *intPoolPool) get() *intPool { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(poolOfIntPools.pools) > 0 { - ip := ipp.pools[len(ipp.pools)-1] - ipp.pools = ipp.pools[:len(ipp.pools)-1] - return ip - } - return newIntPool() -} - -// put a pool that has been allocated with get. -func (ipp *intPoolPool) put(ip *intPool) { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(ipp.pools) < cap(ipp.pools) { - ipp.pools = append(ipp.pools, ip) - } -} diff --git a/core/vm/intpool_test.go b/core/vm/intpool_test.go deleted file mode 100644 index 6c0d00f3ce..0000000000 --- a/core/vm/intpool_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "testing" -) - -func TestIntPoolPoolGet(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - - nip := poolOfIntPools.get() - if nip == nil { - t.Fatalf("Invalid pool allocation") - } -} - -func TestIntPoolPoolPut(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - - nip := poolOfIntPools.get() - if len(poolOfIntPools.pools) != 0 { - t.Fatalf("Pool got added to list when none should have been") - } - - poolOfIntPools.put(nip) - if len(poolOfIntPools.pools) == 0 { - t.Fatalf("Pool did not get added to list when one should have been") - } -} - -func TestIntPoolPoolReUse(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - nip := poolOfIntPools.get() - poolOfIntPools.put(nip) - poolOfIntPools.get() - - if len(poolOfIntPools.pools) != 0 { - t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0) - } -} diff --git a/core/vm/logger.go b/core/vm/logger.go index dd93ce3ea3..a5baef918f 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -39,7 +39,6 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } - return cpy } @@ -115,16 +114,16 @@ type Tracer interface { type StructLogger struct { cfg LogConfig - logs []StructLog - changedValues map[common.Address]Storage - output []byte - err error + storage map[common.Address]Storage + logs []StructLog + output []byte + err error } // NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ - changedValues: make(map[common.Address]Storage), + storage: make(map[common.Address]Storage), } if cfg != nil { logger.cfg = *cfg @@ -139,28 +138,12 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea // CaptureState logs a new structured log message and pushes it out to the environment // -// CaptureState also tracks SSTORE ops to track dirty values. +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { return ErrTraceLimitReached } - - // initialise new changed values storage container for this contract - // if not present. - if l.changedValues[contract.Address()] == nil { - l.changedValues[contract.Address()] = make(Storage) - } - - // capture SSTORE opcodes and determine the changed value and store - // it in the local storage container. - if op == SSTORE && stack.len() >= 2 { - var ( - value = common.BigToHash(stack.data[stack.len()-2]) - address = common.BigToHash(stack.data[stack.len()-1]) - ) - l.changedValues[contract.Address()][address] = value - } // Copy a snapshot of the current memory state to a new buffer var mem []byte if !l.cfg.DisableMemory { @@ -172,22 +155,42 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui if !l.cfg.DisableStack { stck = make([]*big.Int, len(stack.Data())) for i, item := range stack.Data() { - stck[i] = new(big.Int).Set(item) + stck[i] = new(big.Int).Set(item.ToBig()) } } - // Copy a snapshot of the current storage to a new container - var storage Storage - if !l.cfg.DisableStorage { - storage = l.changedValues[contract.Address()].Copy() - } var rstack []uint64 if !l.cfg.DisableStack && rStack != nil { rstck := make([]uint64, len(rStack.data)) copy(rstck, rStack.data) } + // Copy a snapshot of the current storage to a new container + var storage Storage + if !l.cfg.DisableStorage { + // initialise new changed values storage container for this contract + // if not present. + if l.storage[contract.Address()] == nil { + l.storage[contract.Address()] = make(Storage) + } + // capture SLOAD opcodes and record the read entry in the local storage + if op == SLOAD && stack.len() >= 1 { + var ( + address = common.Hash(stack.data[stack.len()-1].Bytes32()) + value = env.StateDB.GetState(contract.Address(), address) + ) + l.storage[contract.Address()][address] = value + } + // capture SSTORE opcodes and record the written entry in the local storage. + if op == SSTORE && stack.len() >= 2 { + var ( + value = common.Hash(stack.data[stack.len()-2].Bytes32()) + address = common.Hash(stack.data[stack.len()-1].Bytes32()) + ) + l.storage[contract.Address()][address] = value + } + storage = l.storage[contract.Address()].Copy() + } // create a new snapshot of the EVM. log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, storage, depth, env.StateDB.GetRefund(), err} - l.logs = append(l.logs, log) return nil } diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 12b9011548..ad4b8e6bca 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -62,7 +62,12 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Memory = memory.Data() } if !l.cfg.DisableStack { - log.Stack = stack.Data() + //TODO(@holiman) improve this + logstack := make([]*big.Int, len(stack.Data())) + for i, item := range stack.Data() { + logstack[i] = item.ToBig() + } + log.Stack = logstack log.ReturnStack = rStack.data } return l.encoder.Encode(log) diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index c8579a01a7..444854ba2a 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -23,6 +23,7 @@ import ( "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/core/state" "github.com/celo-org/celo-blockchain/params" + "github.com/holiman/uint256" ) type dummyContractRef struct { @@ -57,15 +58,15 @@ func TestStoreCapture(t *testing.T) { rstack = newReturnStack() contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) ) - stack.push(big.NewInt(1)) - stack.push(big.NewInt(0)) + stack.push(uint256.NewInt().SetUint64(1)) + stack.push(uint256.NewInt()) var index common.Hash logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, contract, 0, nil) - if len(logger.changedValues[contract.Address()]) == 0 { - t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) + if len(logger.storage[contract.Address()]) == 0 { + t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } exp := common.BigToHash(big.NewInt(1)) - if logger.changedValues[contract.Address()][index] != exp { - t.Errorf("expected %x, got %x", exp, logger.changedValues[contract.Address()][index]) + if logger.storage[contract.Address()][index] != exp { + t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index]) } } diff --git a/core/vm/memory.go b/core/vm/memory.go index 7c9797751e..ba5f8485dc 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -18,9 +18,8 @@ package vm import ( "fmt" - "math/big" - "github.com/celo-org/celo-blockchain/common/math" + "github.com/holiman/uint256" ) // Memory implements a simple memory model for the ethereum virtual machine. @@ -50,7 +49,7 @@ func (m *Memory) Set(offset, size uint64, value []byte) { // Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to // 32 bytes. -func (m *Memory) Set32(offset uint64, val *big.Int) { +func (m *Memory) Set32(offset uint64, val *uint256.Int) { // length of store may never be less than offset + size. // The store should be resized PRIOR to setting the memory if offset+32 > uint64(len(m.store)) { @@ -59,7 +58,7 @@ func (m *Memory) Set32(offset uint64, val *big.Int) { // Zero the memory area copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) // Fill in relevant bits - math.ReadBits(val, m.store[offset:offset+32]) + val.WriteToSlice(m.store[offset:]) } // Resize resizes the memory to size diff --git a/core/vm/stack.go b/core/vm/stack.go index 0171ad0dbd..99de4d79c8 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -18,36 +18,36 @@ package vm import ( "fmt" - "math/big" + + "github.com/holiman/uint256" ) // Stack is an object for basic stack operations. Items popped to the stack are // expected to be changed and modified. stack does not take care of adding newly // initialised objects. type Stack struct { - data []*big.Int + data []uint256.Int } func newstack() *Stack { - return &Stack{data: make([]*big.Int, 0, 1024)} + return &Stack{data: make([]uint256.Int, 0, 16)} } -// Data returns the underlying big.Int array. -func (st *Stack) Data() []*big.Int { +// Data returns the underlying uint256.Int array. +func (st *Stack) Data() []uint256.Int { return st.data } -func (st *Stack) push(d *big.Int) { +func (st *Stack) push(d *uint256.Int) { // NOTE push limit (1024) is checked in baseCheck - //stackItem := new(big.Int).Set(d) - //st.data = append(st.data, stackItem) - st.data = append(st.data, d) + st.data = append(st.data, *d) } -func (st *Stack) pushN(ds ...*big.Int) { +func (st *Stack) pushN(ds ...uint256.Int) { + // FIXME: Is there a way to pass args by pointers. st.data = append(st.data, ds...) } -func (st *Stack) pop() (ret *big.Int) { +func (st *Stack) pop() (ret uint256.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return @@ -61,17 +61,17 @@ func (st *Stack) swap(n int) { st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n] } -func (st *Stack) dup(pool *intPool, n int) { - st.push(pool.get().Set(st.data[st.len()-n])) +func (st *Stack) dup(n int) { + st.push(&st.data[st.len()-n]) } -func (st *Stack) peek() *big.Int { - return st.data[st.len()-1] +func (st *Stack) peek() *uint256.Int { + return &st.data[st.len()-1] } // Back returns the n'th item in stack -func (st *Stack) Back(n int) *big.Int { - return st.data[st.len()-n-1] +func (st *Stack) Back(n int) *uint256.Int { + return &st.data[st.len()-n-1] } // Print dumps the content of the stack diff --git a/crypto/crypto.go b/crypto/crypto.go index 8c4870e252..6aad31c4b1 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -24,6 +24,7 @@ import ( "encoding/hex" "errors" "fmt" + "hash" "io" "io/ioutil" "math/big" @@ -51,23 +52,33 @@ var ( var errInvalidPubkey = errors.New("invalid secp256k1 public key") +// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type KeccakState interface { + hash.Hash + Read([]byte) (int, error) +} + // Keccak256 calculates and returns the Keccak256 hash of the input data. func Keccak256(data ...[]byte) []byte { - d := sha3.NewLegacyKeccak256() + b := make([]byte, 32) + d := sha3.NewLegacyKeccak256().(KeccakState) for _, b := range data { d.Write(b) } - return d.Sum(nil) + d.Read(b) + return b } // Keccak256Hash calculates and returns the Keccak256 hash of the input data, // converting it to an internal Hash data structure. func Keccak256Hash(data ...[]byte) (h common.Hash) { - d := sha3.NewLegacyKeccak256() + d := sha3.NewLegacyKeccak256().(KeccakState) for _, b := range data { d.Write(b) } - d.Sum(h[:0]) + d.Read(h[:]) return h } diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 35d0eef34a..9a7c06d7ce 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -8,10 +8,19 @@ package secp256k1 /* #cgo CFLAGS: -I./libsecp256k1 #cgo CFLAGS: -I./libsecp256k1/src/ + +#ifdef __SIZEOF_INT128__ +# define HAVE___INT128 +# define USE_FIELD_5X52 +# define USE_SCALAR_4X64 +#else +# define USE_FIELD_10X26 +# define USE_SCALAR_8X32 +#endif + +#define USE_ENDOMORPHISM #define USE_NUM_NONE -#define USE_FIELD_10X26 #define USE_FIELD_INV_BUILTIN -#define USE_SCALAR_8X32 #define USE_SCALAR_INV_BUILTIN #define NDEBUG #include "./libsecp256k1/src/secp256k1.c" diff --git a/eth/api_backend.go b/eth/api_backend.go index fd18e365d0..4bb2f21d62 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -292,10 +292,14 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } -func (b *EthAPIBackend) RPCGasCap() *big.Int { +func (b *EthAPIBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } +func (b *EthAPIBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections diff --git a/eth/api_tracer.go b/eth/api_tracer.go index e342545b77..09dd4dca71 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -765,10 +765,15 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v // Depending on the tracer type, format and return the output switch tracer := tracer.(type) { case *vm.StructLogger: + // If the result contains a revert reason, return it. + returnVal := fmt.Sprintf("%x", result.Return()) + if len(result.Revert()) > 0 { + returnVal = fmt.Sprintf("%x", result.Revert()) + } return ðapi.ExecutionResult{ Gas: result.UsedGas, Failed: result.Failed(), - ReturnValue: fmt.Sprintf("%x", result.Return()), + ReturnValue: returnVal, StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil diff --git a/eth/config.go b/eth/config.go index d1ea1d9932..d05ccc214f 100644 --- a/eth/config.go +++ b/eth/config.go @@ -42,7 +42,9 @@ var DefaultConfig = Config{ SnapshotCache: 102, GatewayFee: big.NewInt(0), - TxPool: core.DefaultTxPoolConfig, + TxPool: core.DefaultTxPoolConfig, + RPCGasCap: 25000000, + RPCTxFeeCap: 500, // 500 celo Istanbul: *istanbul.DefaultConfig, } @@ -121,7 +123,11 @@ type Config struct { EVMInterpreter string // RPCGasCap is the global gas cap for eth-call variants. - RPCGasCap *big.Int `toml:",omitempty"` + RPCGasCap uint64 `toml:",omitempty"` + + // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for + // send-transction variants. The unit is ether. + RPCTxFeeCap float64 `toml:",omitempty"` // Checkpoint is a hardcoded checkpoint which can be nil. Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 47cdc36783..e06261edec 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -108,7 +108,7 @@ type Downloader struct { rttEstimate uint64 // Round trip time to target for download requests rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops) - Mode SyncMode // Synchronisation mode defining the strategy used (per sync cycle) + mode uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode mux *event.TypeMux // Event multiplexer to announce sync operation events checkpoint uint64 // Checkpoint block number to enforce head against (e.g. fast sync) @@ -287,15 +287,16 @@ func (d *Downloader) Progress() ethereum.SyncProgress { defer d.syncStatsLock.RUnlock() current := uint64(0) + mode := d.getMode() switch { - case d.blockchain != nil && d.Mode == FullSync: + case d.blockchain != nil && mode == FullSync: current = d.blockchain.CurrentBlock().NumberU64() - case d.blockchain != nil && d.Mode == FastSync: + case d.blockchain != nil && mode == FastSync: current = d.blockchain.CurrentFastBlock().NumberU64() case d.lightchain != nil: current = d.lightchain.CurrentHeader().Number.Uint64() default: - log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", d.Mode) + log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode) } log.Debug(fmt.Sprintf("Current head is %v", current)) return ethereum.SyncProgress{ @@ -357,7 +358,9 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode return err } - if errors.Is(err, errInvalidChain) { + if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) || + errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) || + errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) { log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) if d.dropPeer == nil { // The dropPeer method is nil when `--copydb` is used for a local copy. @@ -368,22 +371,7 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode } return err } - - switch err { - case errTimeout, errBadPeer, errStallingPeer, errUnsyncedPeer, - errEmptyHeaderSet, errPeersUnavailable, errTooOld, - errInvalidAncestor: - log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) - if d.dropPeer == nil { - // The dropPeer method is nil when `--copydb` is used for a local copy. - // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored - log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", id) - } else { - d.dropPeer(id) - } - default: - log.Warn("Synchronisation failed, retrying", "err", err) - } + log.Warn("Synchronisation failed, retrying", "err", err) return err } @@ -445,8 +433,8 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode defer d.Cancel() // No matter what, we can't leave the cancel channel open - // Set the requested sync mode, unless it's forbidden - d.Mode = mode + // Atomically set the requested sync mode + atomic.StoreUint32(&d.mode, uint32(mode)) // Retrieve the origin peer and initiate the downloading process p := d.peers.Peer(id) @@ -456,6 +444,10 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode return d.syncWithPeer(p, hash, td) } +func (d *Downloader) getMode() SyncMode { + return SyncMode(atomic.LoadUint32(&d.mode)) +} + // syncWithPeer starts a block synchronization based on the hash chain from the // specified peer and head hash. func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.Int) (err error) { @@ -472,8 +464,9 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I if p.version < 62 { return errTooOld } + mode := d.getMode() - log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", d.Mode) + log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode) defer func(start time.Time) { log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) }(time.Now()) @@ -499,7 +492,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I // Ensure our origin point is below any fast sync pivot point pivot := uint64(0) - if d.Mode == FastSync { + if mode == FastSync { pivot = d.calcPivot(height) rawdb.WriteLastPivotNumber(d.stateDB, pivot) if pivot == 0 { @@ -509,10 +502,10 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } } d.committed = 1 - if d.Mode == FastSync && pivot != 0 { + if mode == FastSync && pivot != 0 { d.committed = 0 } - if d.Mode == FastSync { + if mode == FastSync { // Set the ancient data limitation. // If we are running fast sync, all block data older than ancientLimit will be // written to the ancient store. More recent data will be written to the active @@ -550,7 +543,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } } // Initiate the sync using a concurrent header and content retrieval algorithm - d.queue.Prepare(origin+1, d.Mode) + d.queue.Prepare(origin+1, mode) if d.syncInitHook != nil { d.syncInitHook(origin, height) } @@ -560,9 +553,9 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync func() error { return d.processHeaders(origin+1, pivot, td) }, } - if d.Mode == FastSync { + if mode == FastSync { fetchers = append(fetchers, func() error { return d.processFastSyncContent(latest) }) - } else if d.Mode == FullSync { + } else if mode == FullSync { fetchers = append(fetchers, d.processFullSyncContent) } return d.spawnSync(fetchers) @@ -650,6 +643,7 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) { ttl := d.requestTTL() timeout := time.After(ttl) + mode := d.getMode() for { select { case <-d.cancelCh: @@ -665,10 +659,10 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) { headers := packet.(*headerPack).headers if len(headers) != 1 { p.log.Debug("Multiple headers for single request", "headers", len(headers)) - return nil, errBadPeer + return nil, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) } head := headers[0] - if (d.Mode == FastSync || d.Mode == LightSync) && head.Number.Uint64() < d.checkpoint { + if (mode == FastSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { p.log.Warn("Remote head below checkpoint", "number", head.Number, "hash", head.Hash()) return nil, errUnsyncedPeer } @@ -750,7 +744,8 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) localHeight uint64 remoteHeight = remoteHeader.Number.Uint64() ) - switch d.Mode { + mode := d.getMode() + switch mode { case FullSync: localHeight = d.blockchain.CurrentBlock().NumberU64() case FastSync: @@ -770,7 +765,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) // If we're doing a light sync, ensure the floor doesn't go below the CHT, as // all headers before that point will be missing. - if !d.Mode.SyncFullBlockChain() { + if !mode.SyncFullBlockChain() { // If we don't know the current CHT position, find it if d.genesis == 0 { header := d.lightchain.CurrentHeader() @@ -836,7 +831,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) n := headers[i].Number.Uint64() var known bool - switch d.Mode { + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) case FastSync: @@ -900,7 +895,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) headers := packer.(*headerPack).headers if len(headers) != 1 { p.log.Debug("Multiple headers for single request", "headers", len(headers)) - return 0, errBadPeer + return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) } arrived = true @@ -909,7 +904,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) n := headers[0].Number.Uint64() var known bool - switch d.Mode { + switch mode { case FullSync: known = d.blockchain.HasBlock(h, n) case FastSync: @@ -924,7 +919,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists if header.Number.Uint64() != check { p.log.Debug("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check) - return 0, errBadPeer + return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number) } start = check hash = h @@ -987,8 +982,9 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, } } + mode := d.getMode() getEpochHeaders := func(fromEpochBlock uint64) { - if d.Mode != LightestSync { + if mode != LightestSync { panic("This method should be called only in LightestSync mode") } if fromEpochBlock%epoch != 0 { @@ -1037,7 +1033,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, // Start pulling the header chain skeleton until all is done ancestor := from - if d.Mode == LightestSync { + if mode == LightestSync { if epoch == 0 { panic("Epoch cannot be 0 in IBFT + LightestSync") } @@ -1114,7 +1110,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, if n := len(headers); n > 0 { // Retrieve the current head we're at var head uint64 - if d.Mode == LightSync { + if mode == LightSync { head = d.lightchain.CurrentHeader().Number.Uint64() } else { head = d.blockchain.CurrentFastBlock().NumberU64() @@ -1149,7 +1145,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, } // In all other sync modes, we fetch the block immediately after the current block. // In the lightest sync mode, increment the value by epoch instead. - if d.Mode == LightestSync { + if mode == LightestSync { lastFetchedHeaderNumber := headers[len(headers)-1].Number.Uint64() moreHeaderFetchesPending := getEpochOrNormalHeaders(lastFetchedHeaderNumber + 1) if !moreHeaderFetchesPending { @@ -1171,7 +1167,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, p.log.Trace("All headers delayed, waiting") select { case <-time.After(fsHeaderContCheck): - if d.Mode == LightestSync { + if mode == LightestSync { getEpochOrNormalHeaders(from) } else { getHeaders(from) @@ -1205,7 +1201,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64, case d.headerProcCh <- nil: case <-d.cancelCh: } - return errBadPeer + return fmt.Errorf("%w: header request timed out", errBadPeer) } } } @@ -1499,12 +1495,12 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er var ( rollback uint64 // Zero means no rollback (fine as you can't unroll the genesis) rollbackErr error - mode = d.Mode + mode = d.getMode() ) defer func() { if rollback > 0 { lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 - if d.Mode.SyncFullBlockChain() { + if mode.SyncFullBlockChain() { lastFastBlock = d.blockchain.CurrentFastBlock().Number() lastBlock = d.blockchain.CurrentBlock().Number() } @@ -1513,7 +1509,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) } curFastBlock, curBlock := common.Big0, common.Big0 - if d.Mode.SyncFullBlockChain() { + if mode.SyncFullBlockChain() { curFastBlock = d.blockchain.CurrentFastBlock().Number() curBlock = d.blockchain.CurrentBlock().Number() } @@ -1554,7 +1550,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er // L: Sync begins, and finds common ancestor at 11 // L: Request new headers up from 11 (R's TD was higher, it must have something) // R: Nothing to give - if d.Mode.SyncFullBlockChain() { + if mode.SyncFullBlockChain() { head := d.blockchain.CurrentBlock() if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.NumberU64())) > 0 { rollbackErr = errStallingPeer @@ -1568,8 +1564,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er // This check cannot be executed "as is" for full imports, since blocks may still be // queued for processing when the header download completes. However, as long as the // peer gave us something useful, we're already happy/progressed (above check). - - if d.Mode == FastSync || d.Mode == LightSync { + if mode == FastSync || mode == LightSync { head := d.lightchain.CurrentHeader() if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 { rollbackErr = errStallingPeer @@ -1598,13 +1593,13 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er chunk := headers[:limit] // In case of header only syncing, validate the chunk immediately - if mode == FastSync || !mode.SyncFullBlockChain() { + if mode == FastSync || !mode.SyncFullBlockChain() { // mode != FullSync ? // If we're importing pure headers, verify based on their recentness frequency := fsHeaderCheckFrequency if chunk[len(chunk)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot { frequency = 1 } - if n, err := d.lightchain.InsertHeaderChain(chunk, frequency, d.Mode.SyncFullHeaderChain()); err != nil { + if n, err := d.lightchain.InsertHeaderChain(chunk, frequency, mode.SyncFullHeaderChain()); err != nil { rollbackErr = err // If some headers were inserted, track them as uncertain @@ -1623,7 +1618,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er } } // Unless we're doing light chains, schedule the headers for associated content retrieval - if d.Mode.SyncFullBlockChain() { + if mode.SyncFullBlockChain() { // If we've reached the allowed number of pending headers, stall a bit for d.queue.PendingBlocks() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { select { @@ -1638,7 +1633,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er if len(inserts) != len(chunk) { log.Debug("Stale headers") rollbackErr = errBadPeer - return errBadPeer + return fmt.Errorf("%w: stale headers", errBadPeer) } } headers = headers[limit:] diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 08faff947e..d5ffa0f25a 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -517,7 +517,7 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng blocks += length - common receipts += length - common } - if tester.downloader.Mode == LightSync { + if tester.downloader.getMode() == LightSync { blocks, receipts = 1, 1 } if hs := len(tester.ownHeaders) + len(tester.ancientHeaders) - 1; hs != headers { diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index accdfbdf1a..4ca00f3894 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -19,7 +19,8 @@ package downloader import "fmt" // SyncMode represents the synchronisation mode of the downloader. -type SyncMode int +// It is a uint32 as it is used with atomic operations. +type SyncMode uint32 const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 91ca343027..b827a32705 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -421,7 +421,7 @@ func (ps *peerSet) Unregister(id string) error { ps.lock.Lock() p, ok := ps.peers[id] if !ok { - defer ps.lock.Unlock() + ps.lock.Unlock() return errNotRegistered } delete(ps.peers, id) diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 069546a8ca..9276060330 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -63,6 +63,10 @@ func (d *Downloader) syncState(root common.Hash) *stateSync { s := newStateSync(d, root) select { case d.stateSyncStart <- s: + // If we tell the statesync to restart with a new root, we also need + // to wait for it to actually also start -- when old requests have timed + // out or been delivered + <-s.started case <-d.quitCh: s.err = errCancelStateFetch close(s.done) @@ -95,15 +99,9 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { finished []*stateReq // Completed or failed requests timeout = make(chan *stateReq) // Timed out active requests ) - defer func() { - // Cancel active request timers on exit. Also set peers to idle so they're - // available for the next sync. - for _, req := range active { - req.timer.Stop() - req.peer.SetNodeDataIdle(len(req.items)) - } - }() + // Run the state sync. + log.Trace("State sync starting", "root", s.root) go s.run() defer s.Cancel() @@ -126,9 +124,11 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { select { // The stateSync lifecycle: case next := <-d.stateSyncStart: + d.spindownStateSync(active, finished, timeout, peerDrop) return next case <-s.done: + d.spindownStateSync(active, finished, timeout, peerDrop) return nil // Send the next finished request to the current sync: @@ -189,11 +189,9 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { // causes valid requests to go missing and sync to get stuck. if old := active[req.peer.id]; old != nil { log.Warn("Busy peer assigned new state fetch", "peer", old.peer.id) - - // Make sure the previous one doesn't get siletly lost + // Move the previous request to the finished set old.timer.Stop() old.dropped = true - finished = append(finished, old) } // Start a timer to notify the sync loop if the peer stalled. @@ -210,6 +208,46 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { } } +// spindownStateSync 'drains' the outstanding requests; some will be delivered and other +// will time out. This is to ensure that when the next stateSync starts working, all peers +// are marked as idle and de facto _are_ idle. +func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) { + log.Trace("State sync spinning down", "active", len(active), "finished", len(finished)) + + for len(active) > 0 { + var ( + req *stateReq + reason string + ) + select { + // Handle (drop) incoming state packs: + case pack := <-d.stateCh: + req = active[pack.PeerId()] + reason = "delivered" + // Handle dropped peer connections: + case p := <-peerDrop: + req = active[p.id] + reason = "peerdrop" + // Handle timed-out requests: + case req = <-timeout: + reason = "timeout" + } + if req == nil { + continue + } + req.peer.log.Trace("State peer marked idle (spindown)", "req.items", len(req.items), "reason", reason) + req.timer.Stop() + delete(active, req.peer.id) + req.peer.SetNodeDataIdle(len(req.items)) + } + // The 'finished' set contains deliveries that we were going to pass to processing. + // Those are now moot, but we still need to set those peers as idle, which would + // otherwise have been done after processing + for _, req := range finished { + req.peer.SetNodeDataIdle(len(req.items)) + } +} + // stateSync schedules requests for downloading a particular state trie defined // by a given state root. type stateSync struct { @@ -222,11 +260,15 @@ type stateSync struct { numUncommitted int bytesUncommitted int + started chan struct{} // Started is signalled once the sync loop starts + deliver chan *stateReq // Delivery channel multiplexing peer responses cancel chan struct{} // Channel to signal a termination request cancelOnce sync.Once // Ensures cancel only ever gets called once done chan struct{} // Channel to signal termination completion err error // Any error hit during sync (set before completion) + + root common.Hash } // stateTask represents a single trie node download task, containing a set of @@ -246,6 +288,8 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { deliver: make(chan *stateReq), cancel: make(chan struct{}), done: make(chan struct{}), + started: make(chan struct{}), + root: root, } } @@ -276,6 +320,7 @@ func (s *stateSync) Cancel() error { // pushed here async. The reason is to decouple processing from data receipt // and timeouts. func (s *stateSync) loop() (err error) { + close(s.started) // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) peerSub := s.d.peers.SubscribeNewPeers(newPeer) @@ -331,11 +376,11 @@ func (s *stateSync) loop() (err error) { } // Process all the received blobs and check for stale delivery delivered, err := s.process(req) + req.peer.SetNodeDataIdle(delivered) if err != nil { log.Warn("Node data write error", "err", err) return err } - req.peer.SetNodeDataIdle(delivered) } } return nil @@ -372,7 +417,7 @@ func (s *stateSync) assignTasks() { // If the peer was assigned tasks to fetch, send the network request if len(req.items) > 0 { - req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(req.items)) + req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(req.items), "root", s.root) select { case s.d.trackStateReq <- req: req.peer.FetchNodeData(req.items) diff --git a/eth/gen_config.go b/eth/gen_config.go index ebb0a172a0..f61f431f5a 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -43,6 +43,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TrieCleanCache int TrieDirtyCache int TrieTimeout time.Duration + SnapshotCache int Miner miner.Config TxPool core.TxPoolConfig EnablePreimageRecording bool @@ -50,7 +51,8 @@ func (c Config) MarshalTOML() (interface{}, error) { DocRoot string `toml:"-"` EWASMInterpreter string EVMInterpreter string - RPCGasCap *big.Int `toml:",omitempty"` + RPCGasCap uint64 `toml:",omitempty"` + RPCTxFeeCap float64 `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideChurrito *big.Int `toml:",omitempty"` @@ -83,6 +85,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TrieCleanCache = c.TrieCleanCache enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout + enc.SnapshotCache = c.SnapshotCache enc.Miner = c.Miner enc.TxPool = c.TxPool enc.EnablePreimageRecording = c.EnablePreimageRecording @@ -91,6 +94,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.EWASMInterpreter = c.EWASMInterpreter enc.EVMInterpreter = c.EVMInterpreter enc.RPCGasCap = c.RPCGasCap + enc.RPCTxFeeCap = c.RPCTxFeeCap enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle enc.OverrideChurrito = c.OverrideChurrito @@ -127,6 +131,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TrieCleanCache *int TrieDirtyCache *int TrieTimeout *time.Duration + SnapshotCache *int Miner *miner.Config TxPool *core.TxPoolConfig EnablePreimageRecording *bool @@ -134,7 +139,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { DocRoot *string `toml:"-"` EWASMInterpreter *string EVMInterpreter *string - RPCGasCap *big.Int `toml:",omitempty"` + RPCGasCap *uint64 `toml:",omitempty"` + RPCTxFeeCap *float64 `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideChurrito *big.Int `toml:",omitempty"` @@ -222,6 +228,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TrieTimeout != nil { c.TrieTimeout = *dec.TrieTimeout } + if dec.SnapshotCache != nil { + c.SnapshotCache = *dec.SnapshotCache + } if dec.Miner != nil { c.Miner = *dec.Miner } @@ -244,7 +253,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { c.EVMInterpreter = *dec.EVMInterpreter } if dec.RPCGasCap != nil { - c.RPCGasCap = dec.RPCGasCap + c.RPCGasCap = *dec.RPCGasCap + } + if dec.RPCTxFeeCap != nil { + c.RPCTxFeeCap = *dec.RPCTxFeeCap } if dec.Checkpoint != nil { c.Checkpoint = dec.Checkpoint diff --git a/eth/handler_test.go b/eth/handler_test.go index b32b9bc6f9..df234292f2 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -607,13 +607,16 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { select { case <-doneCh: received++ - - case <-time.After(time.Second): + if received > broadcastExpected { + // We can bail early here + t.Errorf("broadcast count mismatch: have %d > want %d", received, broadcastExpected) + return + } + case <-time.After(2 * time.Second): if received != broadcastExpected { t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected) } return - case err = <-errCh: t.Fatalf("broadcast failed: %v", err) } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 4922aca4b0..9bb376698b 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -162,7 +162,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int { log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) return new(big.Int) } - return sw.stack.Data()[len(sw.stack.Data())-idx-1] + return sw.stack.Back(idx).ToBig() } // pushObject assembles a JSVM object wrapping a swappable stack and pushes it diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 13aa1e1bf0..e1b5d7f280 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -354,6 +354,7 @@ func (s *Service) loop(ctx context.Context) { if err = s.report(conn, sendCh); err != nil { log.Warn("Initial stats report failed", "err", err) conn.Close() + errTimer.Reset(0) continue } diff --git a/go.mod b/go.mod index 9fec059b6a..133b39c0e2 100644 --- a/go.mod +++ b/go.mod @@ -31,9 +31,10 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 - github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad + github.com/hashicorp/golang-lru v0.5.4 github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff - github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 + github.com/holiman/uint256 v1.1.0 + github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 @@ -51,10 +52,10 @@ require ( github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 github.com/rjeczalik/notify v0.9.1 - github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect github.com/shopspring/decimal v1.2.0 - github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible + github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect @@ -70,7 +71,7 @@ require ( golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce - gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200603215123-a4a8cb9d2cbc + gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 gopkg.in/yaml.v2 v2.2.7 // indirect gotest.tools v2.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index e8895d28e7..77b22bdeca 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -51,9 +53,6 @@ github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72 h1:fUmDBbSvv github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72/go.mod h1:OEE5igu/CDjGegM1Jn6ZMo7R6LlV/JChAkjfQQIRLpg= github.com/celo-org/celo-bls-go v0.2.4 h1:V1y92kM5IRJWQZ6DCwqiKLW7swmUA5y/dPJ9YbU4HfA= github.com/celo-org/celo-bls-go v0.2.4/go.mod h1:eXUCLXu5F1yfd3M+3VaUk5ZUXaA0sLK2rWdLC1Cfaqo= -github.com/celo-org/mobile v0.0.0-20201127114005-6a1221213dcf h1:vjXHjR4gf41rdPgg3XafcYgpxrj28zIAZ89KDTStL6U= -github.com/celo-org/mobile v0.0.0-20201127114005-6a1221213dcf/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -github.com/celo-org/mobile v0.0.0-20210324213558-66ac87d7fb95 h1:X0tWCNjfHVI3NkXhfHPXdQA/Hdi4XexGDER9QPh3V9A= github.com/celo-org/mobile v0.0.0-20210324213558-66ac87d7fb95/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -91,6 +90,8 @@ github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -99,7 +100,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c h1:zqAKixg3cTcIasAMJV+EcfVbWwLpOZ7LeoWJvcuD/5Q= github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -109,14 +109,17 @@ github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDF github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po= -github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff h1:LeVKjw8pcDQj7WVVnbFvbD7ovcv+r/l15ka1NH6Lswc= github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff/go.mod h1:Feit0l8NcNO4g69XNjwvsR0LGcwMMfzI1TF253rOIlQ= +github.com/holiman/uint256 v1.1.0 h1:Iye6ze0DW9s+7EMn8y6Q4ebegDzpu28JQHEVM1Bq+Wg= +github.com/holiman/uint256 v1.1.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 h1:DqD8eigqlUm0+znmx7zhL0xvTW3+e1jCekJMfBUADWI= -github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= +github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= @@ -189,13 +192,12 @@ github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9Ac github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible h1:+gAR1bMhuoQnZMTWFIvp7ukynULPsteLzG+siZKLtD8= -github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= @@ -211,15 +213,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -227,9 +226,9 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -244,6 +243,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= @@ -256,10 +256,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -269,8 +267,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200603215123-a4a8cb9d2cbc h1:17cdygvFw3DEyNMh81Bk687W74d5pcC5qEKQICv9N6g= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200603215123-a4a8cb9d2cbc/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= diff --git a/internal/build/azure.go b/internal/build/azure.go index 7862842650..9c9cc2dcc5 100644 --- a/internal/build/azure.go +++ b/internal/build/azure.go @@ -26,7 +26,7 @@ import ( ) // AzureBlobstoreConfig is an authentication and configuration struct containing -// the data needed by the Azure SDK to interact with a speicifc container in the +// the data needed by the Azure SDK to interact with a specific container in the // blobstore. type AzureBlobstoreConfig struct { Account string // Account name to authorize API requests with @@ -71,26 +71,35 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig) // AzureBlobstoreList lists all the files contained within an azure blobstore. func AzureBlobstoreList(config AzureBlobstoreConfig) ([]azblob.BlobItem, error) { - credential, err := azblob.NewSharedKeyCredential(config.Account, config.Token) - if err != nil { - return nil, err + credential := azblob.NewAnonymousCredential() + if len(config.Token) > 0 { + c, err := azblob.NewSharedKeyCredential(config.Account, config.Token) + if err != nil { + return nil, err + } + credential = c } - pipeline := azblob.NewPipeline(credential, azblob.PipelineOptions{}) u, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net", config.Account)) service := azblob.NewServiceURL(*u, pipeline) + var allBlobs []azblob.BlobItem // List all the blobs from the container and return them container := service.NewContainerURL(config.Container) + nextMarker := azblob.Marker{} + for nextMarker.NotDone() { + res, err := container.ListBlobsFlatSegment(context.Background(), nextMarker, azblob.ListBlobsSegmentOptions{ + MaxResults: 5000, // The server only gives max 5K items + }) + if err != nil { + return nil, err + } + allBlobs = append(allBlobs, res.Segment.BlobItems...) + nextMarker = res.NextMarker - res, err := container.ListBlobsFlatSegment(context.Background(), azblob.Marker{}, azblob.ListBlobsSegmentOptions{ - MaxResults: 1024 * 1024 * 1024, // Yes, fetch all of them - }) - if err != nil { - return nil, err } - return res.Segment.BlobItems, nil + return allBlobs, nil } // AzureBlobstoreDelete iterates over a list of files to delete and removes them @@ -121,6 +130,7 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []azblob.BlobItem) if _, err := blockblob.Delete(context.Background(), azblob.DeleteSnapshotsOptionInclude, azblob.BlobAccessConditions{}); err != nil { return err } + fmt.Printf("deleted %s (%s)\n", blob.Name, blob.Properties.LastModified) } return nil } diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 9bf2ed3e90..8c96c8538b 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -235,7 +235,9 @@ func Setup(ctx *cli.Context) error { } address := fmt.Sprintf("%s:%d", listenHost, port) - StartPProf(address) + // This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name. + // It cannot be imported because it will cause a cyclical dependency. + StartPProf(address, !ctx.GlobalIsSet("metrics.addr")) } return nil } @@ -305,10 +307,12 @@ func getConsoleLogFormat(consoleFormat string, usecolor bool) log.Format { panic(fmt.Sprintf("Unexpected value for \"%s\" flag: \"%s\"", consoleFormatFlag.Name, consoleFormat)) } -func StartPProf(address string) { +func StartPProf(address string, withMetrics bool) { // Hook go-metrics into expvar on any /debug/metrics request, load all vars // from the registry into expvar, and execute regular expvar handler. - exp.Exp(metrics.DefaultRegistry) + if withMetrics { + exp.Exp(metrics.DefaultRegistry) + } http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) go func() { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b0d112de14..83c0d4d39b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -282,7 +282,11 @@ func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (a // NewAccount will create a new account and returns the address for the new account. func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { - acc, err := fetchKeystore(s.am).NewAccount(password) + ks, err := fetchKeystore(s.am) + if err != nil { + return common.Address{}, err + } + acc, err := ks.NewAccount(password) if err == nil { log.Info("Your new key was generated", "address", acc.Address) log.Warn("Please backup your key file!", "path", acc.URL.Path) @@ -293,8 +297,11 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) } // fetchKeystore retrieves the encrypted keystore from the account manager. -func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { - return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) +func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) { + if ks := am.Backends(keystore.KeyStoreType); len(ks) > 0 { + return ks[0].(*keystore.KeyStore), nil + } + return nil, errors.New("local keystore not used") } // ImportRawKey stores the given hex encoded ECDSA key into the key directory, @@ -304,7 +311,11 @@ func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (commo if err != nil { return common.Address{}, err } - acc, err := fetchKeystore(s.am).ImportECDSA(key, password) + ks, err := fetchKeystore(s.am) + if err != nil { + return common.Address{}, err + } + acc, err := ks.ImportECDSA(key, password) return acc.Address, err } @@ -328,7 +339,11 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre } else { d = time.Duration(*duration) * time.Second } - err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d) + ks, err := fetchKeystore(s.am) + if err != nil { + return false, err + } + err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d) if err != nil { log.Warn("Failed account unlock attempt", "address", addr, "err", err) } @@ -337,7 +352,10 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre // LockAccount will lock the account associated with the given address when it's unlocked. func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { - return fetchKeystore(s.am).Lock(addr) == nil + if ks, err := fetchKeystore(s.am); err == nil { + return ks.Lock(addr) == nil + } + return false } // signTransaction sets defaults and signs the given transaction @@ -698,7 +716,7 @@ type CallArgs struct { } // ToMessage converts CallArgs to the Message type used by the core evm -func (args *CallArgs) ToMessage(globalGasCap *big.Int) types.Message { +func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { // Set sender address or use zero address if none specified. var addr common.Address if args.From != nil { @@ -706,13 +724,16 @@ func (args *CallArgs) ToMessage(globalGasCap *big.Int) types.Message { } // Set default gas & gas price if none were set - gas := uint64(math.MaxUint64 / 2) + gas := globalGasCap + if gas == 0 { + gas = uint64(math.MaxUint64 / 2) + } if args.Gas != nil { gas = uint64(*args.Gas) } - if globalGasCap != nil && globalGasCap.Uint64() < gas { + if globalGasCap != 0 && globalGasCap < gas { log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) - gas = globalGasCap.Uint64() + gas = globalGasCap } gasPrice := new(big.Int) if args.GasPrice != nil { @@ -747,7 +768,7 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -817,7 +838,10 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo if evm.Cancelled() { return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - return result, err + if err != nil { + return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas()) + } + return result, nil } func newRevertError(result *core.ExecutionResult) *revertError { @@ -872,7 +896,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr return result.Return(), result.Err } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( lo uint64 = params.TxGas - 1 @@ -895,9 +919,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash hi = core.CalcGasLimit(block, statedb) } - if gasCap != nil && hi > gasCap.Uint64() { + // Recap the highest gas allowance with specified gascap. + if gasCap != 0 && hi > gasCap { log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) - hi = gasCap.Uint64() + hi = gasCap } cap = hi @@ -916,7 +941,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) if err != nil { - if err == core.ErrIntrinsicGas { + if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } return true, nil, err // Bail out @@ -930,7 +955,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is - // assigened. Return the error directly, don't struggle any more. + // assigned. Return the error directly, don't struggle any more. if err != nil { return 0, err } @@ -1528,6 +1553,18 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { // SubmitTransaction is a helper function that submits tx to txPool and logs a message. func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { + // If the transaction fee cap is already specified, ensure the + // fee of the given transaction is _reasonable_. + if b.RPCTxFeeCap() != 0 { + currencyManager, err := NewCurrencyManager(ctx, b) + if err != nil { + return common.Hash{}, err + } + feeCap := GetWei(b.RPCTxFeeCap()) + if currencyManager.CmpValues(tx.Fee(), tx.FeeCurrency(), feeCap, nil) > 0 { + return common.Hash{}, fmt.Errorf("tx fee (%d celo) exceeds the configured cap (%d celo)", tx.Fee().Uint64(), int64(b.RPCTxFeeCap())) + } + } if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index ab5703e03d..84376aa078 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -46,7 +46,8 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool - RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection // Blockchain API SetHead(number uint64) diff --git a/internal/ethapi/util.go b/internal/ethapi/util.go new file mode 100644 index 0000000000..07af963bc5 --- /dev/null +++ b/internal/ethapi/util.go @@ -0,0 +1,29 @@ +package ethapi + +import ( + "context" + "math/big" + + "github.com/celo-org/celo-blockchain/contract_comm/currency" + "github.com/celo-org/celo-blockchain/params" + "github.com/celo-org/celo-blockchain/rpc" +) + +// NewCurrencyManager creates and returns a currencyManager pointing to the latest block +// from the underlying chain from the Backend. +func NewCurrencyManager(ctx context.Context, b Backend) (*currency.CurrencyManager, error) { + stateDb, header, err := b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + return currency.NewManager( + header, + stateDb), nil +} + +// GetWei converts a celo float to a big.Int Wei representation +func GetWei(celo float64) *big.Int { + floatWei := new(big.Float).Mul(big.NewFloat(params.Ether), big.NewFloat(celo)) + wei, _ := floatWei.Int(nil) + return wei +} diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go new file mode 100644 index 0000000000..23c748cae9 --- /dev/null +++ b/internal/utesting/utesting.go @@ -0,0 +1,190 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package utesting provides a standalone replacement for package testing. +// +// This package exists because package testing cannot easily be embedded into a +// standalone go program. It provides an API that mirrors the standard library +// testing API. +package utesting + +import ( + "bytes" + "fmt" + "io" + "regexp" + "runtime" + "sync" + "time" +) + +// Test represents a single test. +type Test struct { + Name string + Fn func(*T) +} + +// Result is the result of a test execution. +type Result struct { + Name string + Failed bool + Output string + Duration time.Duration +} + +// MatchTests returns the tests whose name matches a regular expression. +func MatchTests(tests []Test, expr string) []Test { + var results []Test + re, err := regexp.Compile(expr) + if err != nil { + return nil + } + for _, test := range tests { + if re.MatchString(test.Name) { + results = append(results, test) + } + } + return results +} + +// RunTests executes all given tests in order and returns their results. +// If the report writer is non-nil, a test report is written to it in real time. +func RunTests(tests []Test, report io.Writer) []Result { + results := make([]Result, len(tests)) + for i, test := range tests { + start := time.Now() + results[i].Name = test.Name + results[i].Failed, results[i].Output = Run(test) + results[i].Duration = time.Since(start) + if report != nil { + printResult(results[i], report) + } + } + return results +} + +func printResult(r Result, w io.Writer) { + pd := r.Duration.Truncate(100 * time.Microsecond) + if r.Failed { + fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd) + fmt.Fprintln(w, r.Output) + } else { + fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd) + } +} + +// CountFailures returns the number of failed tests in the result slice. +func CountFailures(rr []Result) int { + count := 0 + for _, r := range rr { + if r.Failed { + count++ + } + } + return count +} + +// Run executes a single test. +func Run(test Test) (bool, string) { + t := new(T) + done := make(chan struct{}) + go func() { + defer close(done) + defer func() { + if err := recover(); err != nil { + buf := make([]byte, 4096) + i := runtime.Stack(buf, false) + t.Logf("panic: %v\n\n%s", err, buf[:i]) + t.Fail() + } + }() + test.Fn(t) + }() + <-done + return t.failed, t.output.String() +} + +// T is the value given to the test function. The test can signal failures +// and log output by calling methods on this object. +type T struct { + mu sync.Mutex + failed bool + output bytes.Buffer +} + +// FailNow marks the test as having failed and stops its execution by calling +// runtime.Goexit (which then runs all deferred calls in the current goroutine). +func (t *T) FailNow() { + t.Fail() + runtime.Goexit() +} + +// Fail marks the test as having failed but continues execution. +func (t *T) Fail() { + t.mu.Lock() + defer t.mu.Unlock() + t.failed = true +} + +// Failed reports whether the test has failed. +func (t *T) Failed() bool { + t.mu.Lock() + defer t.mu.Unlock() + return t.failed +} + +// Log formats its arguments using default formatting, analogous to Println, and records +// the text in the error log. +func (t *T) Log(vs ...interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + fmt.Fprintln(&t.output, vs...) +} + +// Logf formats its arguments according to the format, analogous to Printf, and records +// the text in the error log. A final newline is added if not provided. +func (t *T) Logf(format string, vs ...interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + if len(format) == 0 || format[len(format)-1] != '\n' { + format += "\n" + } + fmt.Fprintf(&t.output, format, vs...) +} + +// Error is equivalent to Log followed by Fail. +func (t *T) Error(vs ...interface{}) { + t.Log(vs...) + t.Fail() +} + +// Errorf is equivalent to Logf followed by Fail. +func (t *T) Errorf(format string, vs ...interface{}) { + t.Logf(format, vs...) + t.Fail() +} + +// Fatal is equivalent to Log followed by FailNow. +func (t *T) Fatal(vs ...interface{}) { + t.Log(vs...) + t.FailNow() +} + +// Fatalf is equivalent to Logf followed by FailNow. +func (t *T) Fatalf(format string, vs ...interface{}) { + t.Logf(format, vs...) + t.FailNow() +} diff --git a/internal/utesting/utesting_test.go b/internal/utesting/utesting_test.go new file mode 100644 index 0000000000..1403a5c8f7 --- /dev/null +++ b/internal/utesting/utesting_test.go @@ -0,0 +1,55 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utesting + +import ( + "strings" + "testing" +) + +func TestTest(t *testing.T) { + tests := []Test{ + { + Name: "successful test", + Fn: func(t *T) {}, + }, + { + Name: "failing test", + Fn: func(t *T) { + t.Log("output") + t.Error("failed") + }, + }, + { + Name: "panicking test", + Fn: func(t *T) { + panic("oh no") + }, + }, + } + results := RunTests(tests, nil) + + if results[0].Failed || results[0].Output != "" { + t.Fatalf("wrong result for successful test: %#v", results[0]) + } + if !results[1].Failed || results[1].Output != "output\nfailed\n" { + t.Fatalf("wrong result for failing test: %#v", results[1]) + } + if !results[2].Failed || !strings.HasPrefix(results[2].Output, "panic: oh no\n") { + t.Fatalf("wrong result for panicking test: %#v", results[2]) + } +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 36d1c59b39..7c8989e15d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -155,7 +155,8 @@ web3._extend({ new web3._extend.Method({ name: 'accountRange', call: 'debug_accountRange', - params: 2 + params: 6, + inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null, null, null, null], }), new web3._extend.Method({ name: 'printBlock', diff --git a/les/api_backend.go b/les/api_backend.go index 322e001cbc..5fc55d9dac 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -266,10 +266,14 @@ func (b *LesApiBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } -func (b *LesApiBackend) RPCGasCap() *big.Int { +func (b *LesApiBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } +func (b *LesApiBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + func (b *LesApiBackend) BloomStatus() (uint64, uint64) { if b.eth.bloomIndexer == nil { return 0, 0 diff --git a/les/checkpointoracle/oracle.go b/les/checkpointoracle/oracle.go index c102718c7e..7dddf26cc6 100644 --- a/les/checkpointoracle/oracle.go +++ b/les/checkpointoracle/oracle.go @@ -21,7 +21,9 @@ package checkpointoracle import ( "encoding/binary" + "sync" "sync/atomic" + "time" "github.com/celo-org/celo-blockchain/accounts/abi/bind" "github.com/celo-org/celo-blockchain/common" @@ -40,6 +42,11 @@ type CheckpointOracle struct { running int32 // Flag whether the contract backend is set or not getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint + + checkMu sync.Mutex // Mutex to sync access to the fields below + lastCheckTime time.Time // Time we last checked the checkpoint + lastCheckPoint *params.TrustedCheckpoint // The last stable checkpoint + lastCheckPointHeight uint64 // The height of last stable checkpoint } // New creates a checkpoint oracle handler with given configs and callback. @@ -88,6 +95,12 @@ func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle { // StableCheckpoint returns the stable checkpoint which was generated by local // indexers and announced by trusted signers. func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) { + oracle.checkMu.Lock() + defer oracle.checkMu.Unlock() + if time.Since(oracle.lastCheckTime) < 1*time.Minute { + return oracle.lastCheckPoint, oracle.lastCheckPointHeight + } + // Look it up properly // Retrieve the latest checkpoint from the contract, abort if empty latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil) if err != nil || (latest == 0 && hash == [32]byte{}) { @@ -103,6 +116,9 @@ func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, u // // In both cases, no stable checkpoint will be returned. if local.HashEqual(hash) { + oracle.lastCheckTime = time.Now() + oracle.lastCheckPointHeight = height.Uint64() + oracle.lastCheckPoint = &local return &local, height.Uint64() } return nil, 0 diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 1bf2d670bf..82301016d6 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -8,6 +8,7 @@ import ( "net/http" "sync" + "github.com/celo-org/celo-blockchain/log" "github.com/celo-org/celo-blockchain/metrics" "github.com/celo-org/celo-blockchain/metrics/prometheus" ) @@ -52,6 +53,20 @@ func ExpHandler(r metrics.Registry) http.Handler { return http.HandlerFunc(e.expHandler) } +// Setup starts a dedicated metrics server at the given address. +// This function enables metrics reporting separate from pprof. +func Setup(address string) { + m := http.NewServeMux() + m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) + m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) + log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) + go func() { + if err := http.ListenAndServe(address, m); err != nil { + log.Error("Failure in running metrics server", "err", err) + } + }() +} + func (exp *exp) getInt(name string) *expvar.Int { var v *expvar.Int exp.expvarLock.Lock() diff --git a/mobile/geth.go b/mobile/geth.go index 409ebf187c..4ddc073548 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -177,7 +177,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { } if config.PprofAddress != "" { - debug.StartPProf(config.PprofAddress) + debug.StartPProf(config.PprofAddress, true) } // Create the empty networking stack diff --git a/node/config.go b/node/config.go index 8553f7508f..1e5383caa4 100644 --- a/node/config.go +++ b/node/config.go @@ -103,11 +103,11 @@ type Config struct { // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or // relative), then that specific path is enforced. An empty path disables IPC. - IPCPath string `toml:",omitempty"` + IPCPath string // HTTPHost is the host interface on which to start the HTTP RPC server. If this // field is empty, no HTTP API endpoint will be started. - HTTPHost string `toml:",omitempty"` + HTTPHost string // HTTPPort is the TCP port number on which to start the HTTP RPC server. The // default zero value is/ valid and will pick a port number randomly (useful @@ -131,7 +131,7 @@ type Config struct { // HTTPModules is a list of API modules to expose via the HTTP RPC interface. // If the module list is empty, all RPC API endpoints designated public will be // exposed. - HTTPModules []string `toml:",omitempty"` + HTTPModules []string // HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC // interface. @@ -139,7 +139,7 @@ type Config struct { // WSHost is the host interface on which to start the websocket RPC server. If // this field is empty, no websocket API endpoint will be started. - WSHost string `toml:",omitempty"` + WSHost string // WSPort is the TCP port number on which to start the websocket RPC server. The // default zero value is/ valid and will pick a port number randomly (useful for @@ -154,7 +154,7 @@ type Config struct { // WSModules is a list of API modules to expose via the websocket RPC interface. // If the module list is empty, all RPC API endpoints designated public will be // exposed. - WSModules []string `toml:",omitempty"` + WSModules []string // WSExposeAll exposes all API modules via the WebSocket RPC interface rather // than just the public ones. @@ -165,7 +165,7 @@ type Config struct { // GraphQLHost is the host interface on which to start the GraphQL server. If this // field is empty, no GraphQL API endpoint will be started. - GraphQLHost string `toml:",omitempty"` + GraphQLHost string // GraphQLPort is the TCP port number on which to start the GraphQL server. The // default zero value is/ valid and will pick a port number randomly (useful diff --git a/p2p/peer.go b/p2p/peer.go index e81943e8ae..430b5793ee 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -372,6 +372,7 @@ func (p *Peer) handle(msg Msg) error { if metrics.Enabled { m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset) metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) + metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) } select { case proto.in <- msg: diff --git a/p2p/rlpx.go b/p2p/rlpx.go index 1d8da4b86c..0314b133af 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -597,6 +597,7 @@ func (rw *rlpxFrameRW) WriteMsg(msg Msg) error { if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode) metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) + metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) } // write header headbuf := make([]byte, 32) diff --git a/rlp/encode.go b/rlp/encode.go index 9c9e8d706d..77b591045d 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -91,13 +91,6 @@ func EncodeToReader(val interface{}) (size int, r io.Reader, err error) { return eb.size(), &encReader{buf: eb}, nil } -type encbuf struct { - str []byte // string data, contains everything except list headers - lheads []*listhead // all list headers - lhsize int // sum of sizes of all encoded list headers - sizebuf []byte // 9-byte auxiliary buffer for uint encoding -} - type listhead struct { offset int // index of this header in string data size int // total size of encoded data (including list headers) @@ -130,19 +123,26 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { return sizesize + 1 } +type encbuf struct { + str []byte // string data, contains everything except list headers + lheads []listhead // all list headers + lhsize int // sum of sizes of all encoded list headers + sizebuf [9]byte // auxiliary buffer for uint encoding + bufvalue reflect.Value // used in writeByteArrayCopy +} + // encbufs are pooled. var encbufPool = sync.Pool{ - New: func() interface{} { return &encbuf{sizebuf: make([]byte, 9)} }, + New: func() interface{} { + var bytes []byte + return &encbuf{bufvalue: reflect.ValueOf(&bytes).Elem()} + }, } func (w *encbuf) reset() { w.lhsize = 0 - if w.str != nil { - w.str = w.str[:0] - } - if w.lheads != nil { - w.lheads = w.lheads[:0] - } + w.str = w.str[:0] + w.lheads = w.lheads[:0] } // encbuf implements io.Writer so it can be passed it into EncodeRLP. @@ -164,7 +164,6 @@ func (w *encbuf) encodeStringHeader(size int) { if size < 56 { w.str = append(w.str, 0x80+byte(size)) } else { - // TODO: encode to w.str directly sizesize := putint(w.sizebuf[1:], uint64(size)) w.sizebuf[0] = 0xB7 + byte(sizesize) w.str = append(w.str, w.sizebuf[:sizesize+1]...) @@ -181,13 +180,29 @@ func (w *encbuf) encodeString(b []byte) { } } -func (w *encbuf) list() *listhead { - lh := &listhead{offset: len(w.str), size: w.lhsize} - w.lheads = append(w.lheads, lh) - return lh +func (w *encbuf) encodeUint(i uint64) { + if i == 0 { + w.str = append(w.str, 0x80) + } else if i < 128 { + // fits single byte + w.str = append(w.str, byte(i)) + } else { + s := putint(w.sizebuf[1:], i) + w.sizebuf[0] = 0x80 + byte(s) + w.str = append(w.str, w.sizebuf[:s+1]...) + } } -func (w *encbuf) listEnd(lh *listhead) { +// list adds a new list header to the header stack. It returns the index +// of the header. The caller must call listEnd with this index after encoding +// the content of the list. +func (w *encbuf) list() int { + w.lheads = append(w.lheads, listhead{offset: len(w.str), size: w.lhsize}) + return len(w.lheads) - 1 +} + +func (w *encbuf) listEnd(index int) { + lh := &w.lheads[index] lh.size = w.size() - lh.offset - lh.size if lh.size < 56 { w.lhsize++ // length encoded into kind tag @@ -230,7 +245,7 @@ func (w *encbuf) toWriter(out io.Writer) (err error) { } } // write the header - enc := head.encode(w.sizebuf) + enc := head.encode(w.sizebuf[:]) if _, err = out.Write(enc); err != nil { return err } @@ -296,7 +311,7 @@ func (r *encReader) next() []byte { return p } r.lhpos++ - return head.encode(r.buf.sizebuf) + return head.encode(r.buf.sizebuf[:]) case r.strpos < len(r.buf.str): // String data at the end, after all list headers. @@ -309,10 +324,7 @@ func (r *encReader) next() []byte { } } -var ( - encoderInterface = reflect.TypeOf(new(Encoder)).Elem() - big0 = big.NewInt(0) -) +var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() // makeWriter creates a writer function for the given type. func makeWriter(typ reflect.Type, ts tags) (writer, error) { @@ -337,7 +349,7 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) { case kind == reflect.Slice && isByte(typ.Elem()): return writeBytes, nil case kind == reflect.Array && isByte(typ.Elem()): - return writeByteArray, nil + return makeByteArrayWriter(typ), nil case kind == reflect.Slice || kind == reflect.Array: return makeSliceWriter(typ, ts) case kind == reflect.Struct: @@ -349,28 +361,13 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) { } } -func isByte(typ reflect.Type) bool { - return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) -} - func writeRawValue(val reflect.Value, w *encbuf) error { w.str = append(w.str, val.Bytes()...) return nil } func writeUint(val reflect.Value, w *encbuf) error { - i := val.Uint() - if i == 0 { - w.str = append(w.str, 0x80) - } else if i < 128 { - // fits single byte - w.str = append(w.str, byte(i)) - } else { - // TODO: encode int to w.str directly - s := putint(w.sizebuf[1:], i) - w.sizebuf[0] = 0x80 + byte(s) - w.str = append(w.str, w.sizebuf[:s+1]...) - } + w.encodeUint(val.Uint()) return nil } @@ -397,13 +394,32 @@ func writeBigIntNoPtr(val reflect.Value, w *encbuf) error { return writeBigInt(&i, w) } +// wordBytes is the number of bytes in a big.Word +const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 + func writeBigInt(i *big.Int, w *encbuf) error { - if cmp := i.Cmp(big0); cmp == -1 { + if i.Sign() == -1 { return fmt.Errorf("rlp: cannot encode negative *big.Int") - } else if cmp == 0 { - w.str = append(w.str, 0x80) - } else { - w.encodeString(i.Bytes()) + } + bitlen := i.BitLen() + if bitlen <= 64 { + w.encodeUint(i.Uint64()) + return nil + } + // Integer is larger than 64 bits, encode from i.Bits(). + // The minimal byte length is bitlen rounded up to the next + // multiple of 8, divided by 8. + length := ((bitlen + 7) & -8) >> 3 + w.encodeStringHeader(length) + w.str = append(w.str, make([]byte, length)...) + index := length + buf := w.str[len(w.str)-length:] + for _, d := range i.Bits() { + for j := 0; j < wordBytes && index > 0; j++ { + index-- + buf[index] = byte(d) + d >>= 8 + } } return nil } @@ -413,7 +429,52 @@ func writeBytes(val reflect.Value, w *encbuf) error { return nil } -func writeByteArray(val reflect.Value, w *encbuf) error { +var byteType = reflect.TypeOf(byte(0)) + +func makeByteArrayWriter(typ reflect.Type) writer { + length := typ.Len() + if length == 0 { + return writeLengthZeroByteArray + } else if length == 1 { + return writeLengthOneByteArray + } + if typ.Elem() != byteType { + return writeNamedByteArray + } + return func(val reflect.Value, w *encbuf) error { + writeByteArrayCopy(length, val, w) + return nil + } +} + +func writeLengthZeroByteArray(val reflect.Value, w *encbuf) error { + w.str = append(w.str, 0x80) + return nil +} + +func writeLengthOneByteArray(val reflect.Value, w *encbuf) error { + b := byte(val.Index(0).Uint()) + if b <= 0x7f { + w.str = append(w.str, b) + } else { + w.str = append(w.str, 0x81, b) + } + return nil +} + +// writeByteArrayCopy encodes byte arrays using reflect.Copy. This is +// the fast path for [N]byte where N > 1. +func writeByteArrayCopy(length int, val reflect.Value, w *encbuf) { + w.encodeStringHeader(length) + offset := len(w.str) + w.str = append(w.str, make([]byte, length)...) + w.bufvalue.SetBytes(w.str[offset:]) + reflect.Copy(w.bufvalue, val) +} + +// writeNamedByteArray encodes byte arrays with named element type. +// This exists because reflect.Copy can't be used with such types. +func writeNamedByteArray(val reflect.Value, w *encbuf) error { if !val.CanAddr() { // Slice requires the value to be addressable. // Make it addressable by copying. diff --git a/rlp/encode_test.go b/rlp/encode_test.go index b4b9e51287..67b3c05422 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -25,6 +25,8 @@ import ( "math/big" "sync" "testing" + + "github.com/celo-org/celo-blockchain/common/math" ) type testEncoder struct { @@ -137,16 +139,43 @@ var encTests = []encTest{ // negative ints are not supported {val: big.NewInt(-1), error: "rlp: cannot encode negative *big.Int"}, - // byte slices, strings + // byte arrays + {val: [0]byte{}, output: "80"}, + {val: [1]byte{0}, output: "00"}, + {val: [1]byte{1}, output: "01"}, + {val: [1]byte{0x7F}, output: "7F"}, + {val: [1]byte{0x80}, output: "8180"}, + {val: [1]byte{0xFF}, output: "81FF"}, + {val: [3]byte{1, 2, 3}, output: "83010203"}, + {val: [57]byte{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + + // named byte type arrays + {val: [0]namedByteType{}, output: "80"}, + {val: [1]namedByteType{0}, output: "00"}, + {val: [1]namedByteType{1}, output: "01"}, + {val: [1]namedByteType{0x7F}, output: "7F"}, + {val: [1]namedByteType{0x80}, output: "8180"}, + {val: [1]namedByteType{0xFF}, output: "81FF"}, + {val: [3]namedByteType{1, 2, 3}, output: "83010203"}, + {val: [57]namedByteType{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + + // byte slices {val: []byte{}, output: "80"}, + {val: []byte{0}, output: "00"}, {val: []byte{0x7E}, output: "7E"}, {val: []byte{0x7F}, output: "7F"}, {val: []byte{0x80}, output: "8180"}, {val: []byte{1, 2, 3}, output: "83010203"}, + // named byte type slices + {val: []namedByteType{}, output: "80"}, + {val: []namedByteType{0}, output: "00"}, + {val: []namedByteType{0x7E}, output: "7E"}, + {val: []namedByteType{0x7F}, output: "7F"}, + {val: []namedByteType{0x80}, output: "8180"}, {val: []namedByteType{1, 2, 3}, output: "83010203"}, - {val: [...]namedByteType{1, 2, 3}, output: "83010203"}, + // strings {val: "", output: "80"}, {val: "\x7E", output: "7E"}, {val: "\x7F", output: "7F"}, @@ -401,3 +430,36 @@ func TestEncodeToReaderReturnToPool(t *testing.T) { } wg.Wait() } + +var sink interface{} + +func BenchmarkIntsize(b *testing.B) { + for i := 0; i < b.N; i++ { + sink = intsize(0x12345678) + } +} + +func BenchmarkPutint(b *testing.B) { + buf := make([]byte, 8) + for i := 0; i < b.N; i++ { + putint(buf, 0x12345678) + sink = buf + } +} + +func BenchmarkEncodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + out := bytes.NewBuffer(make([]byte, 0, 4096)) + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(out, ints); err != nil { + b.Fatal(err) + } + } +} diff --git a/rlp/typecache.go b/rlp/typecache.go index e9a1e3f9e2..6026e1a649 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -210,6 +210,10 @@ func isUint(k reflect.Kind) bool { return k >= reflect.Uint && k <= reflect.Uintptr } +func isByte(typ reflect.Type) bool { + return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) +} + func isByteArray(typ reflect.Type) bool { return (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && isByte(typ.Elem()) } diff --git a/tests/init.go b/tests/init.go index 145b5ae984..88f7908450 100644 --- a/tests/init.go +++ b/tests/init.go @@ -19,6 +19,7 @@ package tests import ( "fmt" "math/big" + "sort" "github.com/celo-org/celo-blockchain/params" ) @@ -142,6 +143,16 @@ var Forks = map[string]*params.ChainConfig{ }, } +// Returns the set of defined fork names +func AvailableForks() []string { + var availableForks []string + for k := range Forks { + availableForks = append(availableForks, k) + } + sort.Strings(availableForks) + return availableForks +} + // UnsupportedForkError is returned when a test requests a fork that isn't implemented. type UnsupportedForkError struct { Name string diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 4abb8dc72e..89ccc7ccab 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -110,11 +110,11 @@ type stTransactionMarshaling struct { PrivateKey hexutil.Bytes } -// getVMConfig takes a fork definition and returns a chain config. +// GetChainConfig takes a fork definition and returns a chain config. // The fork definition can be // - a plain forkname, e.g. `Byzantium`, // - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`. -func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { +func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { var ( splitForks = strings.Split(forkString, "+") ok bool @@ -127,6 +127,9 @@ func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, if eipNum, err := strconv.Atoi(eip); err != nil { return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) } else { + if !vm.ValidEip(eipNum) { + return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) + } eips = append(eips, eipNum) } } @@ -164,7 +167,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bo // RunNoVerify runs a specific subtest and returns the statedb and post-state root func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) { - config, eips, err := getVMConfig(subtest.Fork) + config, eips, err := GetChainConfig(subtest.Fork) if err != nil { return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} } diff --git a/trie/committer.go b/trie/committer.go index cb0e883e72..2bead3fdbc 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/celo-org/celo-blockchain/common" + "github.com/celo-org/celo-blockchain/crypto" "github.com/celo-org/celo-blockchain/rlp" "golang.org/x/crypto/sha3" ) @@ -46,7 +47,7 @@ type leaf struct { // processed sequentially - onleaf will never be called in parallel or out of order. type committer struct { tmp sliceBuffer - sha keccakState + sha crypto.KeccakState onleaf LeafCallback leafCh chan *leaf @@ -57,7 +58,7 @@ var committerPool = sync.Pool{ New: func() interface{} { return &committer{ tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode. - sha: sha3.NewLegacyKeccak256().(keccakState), + sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), } }, } diff --git a/trie/database.go b/trie/database.go index 10cb485581..8e316115f4 100644 --- a/trie/database.go +++ b/trie/database.go @@ -349,14 +349,15 @@ func (db *Database) insert(hash common.Hash, size int, node node) { } // insertPreimage writes a new trie node pre-image to the memory database if it's -// yet unknown. The method will make a copy of the slice. +// yet unknown. The method will NOT make a copy of the slice, +// only use if the preimage will NOT be changed later on. // // Note, this method assumes that the database's lock is held! func (db *Database) insertPreimage(hash common.Hash, preimage []byte) { if _, ok := db.preimages[hash]; ok { return } - db.preimages[hash] = common.CopyBytes(preimage) + db.preimages[hash] = preimage db.preimagesSize += common.StorageSize(common.HashLength + len(preimage)) } diff --git a/trie/hasher.go b/trie/hasher.go index 50a6ed94e6..12c13d2cbf 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -17,21 +17,13 @@ package trie import ( - "hash" "sync" + "github.com/celo-org/celo-blockchain/crypto" "github.com/celo-org/celo-blockchain/rlp" "golang.org/x/crypto/sha3" ) -// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports -// Read to get a variable amount of data from the hash state. Read is faster than Sum -// because it doesn't copy the internal state, but also modifies the internal state. -type keccakState interface { - hash.Hash - Read([]byte) (int, error) -} - type sliceBuffer []byte func (b *sliceBuffer) Write(data []byte) (n int, err error) { @@ -46,7 +38,7 @@ func (b *sliceBuffer) Reset() { // hasher is a type used for the trie Hash operation. A hasher has some // internal preallocated temp space type hasher struct { - sha keccakState + sha crypto.KeccakState tmp sliceBuffer parallel bool // Whether to use paralallel threads when hashing } @@ -56,7 +48,7 @@ var hasherPool = sync.Pool{ New: func() interface{} { return &hasher{ tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode. - sha: sha3.NewLegacyKeccak256().(keccakState), + sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), } }, } diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 06506fd5a6..9a2ae99991 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -249,7 +249,10 @@ func (whisper *Whisper) SetBloomFilter(bloom []byte) error { go func() { // allow some time before all the peers have processed the notification defer whisper.wg.Done() - time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) + ticker := time.NewTicker(time.Duration(whisper.syncAllowance) * time.Second) + defer ticker.Stop() + + <-ticker.C whisper.settings.Store(bloomFilterToleranceIdx, b) }() @@ -269,7 +272,10 @@ func (whisper *Whisper) SetMinimumPoW(val float64) error { go func() { defer whisper.wg.Done() // allow some time before all the peers have processed the notification - time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) + ticker := time.NewTicker(time.Duration(whisper.syncAllowance) * time.Second) + defer ticker.Stop() + + <-ticker.C whisper.settings.Store(minPowToleranceIdx, val) }() diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index c87bdbcc09..ca4b12bee1 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -489,8 +489,10 @@ func TestExpiry(t *testing.T) { // wait till received or timeout var received, expired bool + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() for j := 0; j < 20; j++ { - time.Sleep(100 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) == messagesCount { received = true break @@ -503,7 +505,7 @@ func TestExpiry(t *testing.T) { // wait till expired or timeout for j := 0; j < 20; j++ { - time.Sleep(100 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) == 0 { expired = true break @@ -582,8 +584,10 @@ func TestCustomization(t *testing.T) { // wait till received or timeout var received bool + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() for j := 0; j < 20; j++ { - time.Sleep(100 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) > 1 { received = true break @@ -599,7 +603,7 @@ func TestCustomization(t *testing.T) { if err != nil { t.Fatalf("failed subscribe with seed %d: %s.", seed, err) } - time.Sleep(5 * time.Millisecond) + <-ticker.C mail := f.Retrieve() if len(mail) > 0 { t.Fatalf("received premature mail") @@ -670,8 +674,10 @@ func TestSymmetricSendCycle(t *testing.T) { // wait till received or timeout var received bool + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() for j := 0; j < 200; j++ { - time.Sleep(10 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) > 0 { received = true break @@ -683,7 +689,7 @@ func TestSymmetricSendCycle(t *testing.T) { } // check w.messages() - time.Sleep(5 * time.Millisecond) + <-ticker.C mail1 := filter1.Retrieve() mail2 := filter2.Retrieve() if len(mail2) == 0 { @@ -743,8 +749,10 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { // wait till received or timeout var received bool + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() for j := 0; j < 200; j++ { - time.Sleep(10 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) > 0 { received = true break @@ -756,7 +764,7 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { } // check w.messages() - time.Sleep(5 * time.Millisecond) + <-ticker.C mail := filter.Retrieve() if len(mail) == 0 { t.Fatalf("did not receive message in spite of not setting a public key") @@ -809,8 +817,10 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { // wait till received or timeout var received bool + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() for j := 0; j < 200; j++ { - time.Sleep(10 * time.Millisecond) + <-ticker.C if len(w.Envelopes()) > 0 { received = true break @@ -822,7 +832,7 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { } // check w.messages() - time.Sleep(5 * time.Millisecond) + <-ticker.C mail := filter.Retrieve() if len(mail) > 0 { t.Fatalf("received a message when keys weren't matching")