0c82928981
* core/vm: fix incorrect computation of discount During testing on Yolov1 we found that the way geth calculates the discount is not in line with the specification. Basically what we did is calculate 128 * Bls12381GXMulGas * discount / 1000 whenever we received more than 128 pairs of values. Correct would be to calculate k * Bls12381... for k > 128. * core/vm: better logic for discount calculation * core/vm: better calculation logic, added worstcase benchmarks * core/vm: better benchmarking logic
370 lines
16 KiB
Go
370 lines
16 KiB
Go
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
// precompiledTest defines the input/output pairs for precompiled contract tests.
|
|
type precompiledTest struct {
|
|
Input, Expected string
|
|
Name string
|
|
NoBenchmark bool // Benchmark primarily the worst-cases
|
|
}
|
|
|
|
// precompiledFailureTest defines the input/error pairs for precompiled
|
|
// contract failure tests.
|
|
type precompiledFailureTest struct {
|
|
Input string
|
|
ExpectedError string
|
|
Name string
|
|
}
|
|
|
|
var allPrecompiles = PrecompiledContractsYoloV1
|
|
|
|
// EIP-152 test vectors
|
|
var blake2FMalformedInputTests = []precompiledFailureTest{
|
|
{
|
|
Input: "",
|
|
ExpectedError: errBlake2FInvalidInputLength.Error(),
|
|
Name: "vector 0: empty input",
|
|
},
|
|
{
|
|
Input: "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
|
|
ExpectedError: errBlake2FInvalidInputLength.Error(),
|
|
Name: "vector 1: less than 213 bytes input",
|
|
},
|
|
{
|
|
Input: "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
|
|
ExpectedError: errBlake2FInvalidInputLength.Error(),
|
|
Name: "vector 2: more than 213 bytes input",
|
|
},
|
|
{
|
|
Input: "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002",
|
|
ExpectedError: errBlake2FInvalidFinalFlag.Error(),
|
|
Name: "vector 3: malformed final block indicator flag",
|
|
},
|
|
}
|
|
|
|
func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
|
p := allPrecompiles[common.HexToAddress(addr)]
|
|
in := common.Hex2Bytes(test.Input)
|
|
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
|
nil, new(big.Int), p.RequiredGas(in))
|
|
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
|
if res, err := RunPrecompiledContract(p, in, contract); err != nil {
|
|
t.Error(err)
|
|
} else if common.Bytes2Hex(res) != test.Expected {
|
|
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
|
}
|
|
// Verify that the precompile did not touch the input buffer
|
|
exp := common.Hex2Bytes(test.Input)
|
|
if !bytes.Equal(in, exp) {
|
|
t.Errorf("Precompiled %v modified input data", addr)
|
|
}
|
|
})
|
|
}
|
|
|
|
func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
|
p := allPrecompiles[common.HexToAddress(addr)]
|
|
in := common.Hex2Bytes(test.Input)
|
|
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
|
nil, new(big.Int), p.RequiredGas(in)-1)
|
|
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
|
_, err := RunPrecompiledContract(p, in, contract)
|
|
if err.Error() != "out of gas" {
|
|
t.Errorf("Expected error [out of gas], got [%v]", err)
|
|
}
|
|
// Verify that the precompile did not touch the input buffer
|
|
exp := common.Hex2Bytes(test.Input)
|
|
if !bytes.Equal(in, exp) {
|
|
t.Errorf("Precompiled %v modified input data", addr)
|
|
}
|
|
})
|
|
}
|
|
|
|
func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
|
|
p := allPrecompiles[common.HexToAddress(addr)]
|
|
in := common.Hex2Bytes(test.Input)
|
|
contract := NewContract(AccountRef(common.HexToAddress("31337")),
|
|
nil, new(big.Int), p.RequiredGas(in))
|
|
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
_, err := RunPrecompiledContract(p, in, contract)
|
|
if err.Error() != test.ExpectedError {
|
|
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
|
}
|
|
// Verify that the precompile did not touch the input buffer
|
|
exp := common.Hex2Bytes(test.Input)
|
|
if !bytes.Equal(in, exp) {
|
|
t.Errorf("Precompiled %v modified input data", addr)
|
|
}
|
|
})
|
|
}
|
|
|
|
func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
|
if test.NoBenchmark {
|
|
return
|
|
}
|
|
p := allPrecompiles[common.HexToAddress(addr)]
|
|
in := common.Hex2Bytes(test.Input)
|
|
reqGas := p.RequiredGas(in)
|
|
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
|
nil, new(big.Int), reqGas)
|
|
|
|
var (
|
|
res []byte
|
|
err error
|
|
data = make([]byte, len(in))
|
|
)
|
|
|
|
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
|
|
copy(data, in)
|
|
res, err = RunPrecompiledContract(p, data, contract)
|
|
}
|
|
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)
|
|
return
|
|
}
|
|
if common.Bytes2Hex(res) != test.Expected {
|
|
bench.Error(fmt.Sprintf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)))
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// Benchmarks the sample inputs from the ECRECOVER precompile.
|
|
func BenchmarkPrecompiledEcrecover(bench *testing.B) {
|
|
t := precompiledTest{
|
|
Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
|
|
Expected: "000000000000000000000000ceaccac640adf55b2028469bd36ba501f28b699d",
|
|
Name: "",
|
|
}
|
|
benchmarkPrecompiled("01", t, bench)
|
|
}
|
|
|
|
// Benchmarks the sample inputs from the SHA256 precompile.
|
|
func BenchmarkPrecompiledSha256(bench *testing.B) {
|
|
t := precompiledTest{
|
|
Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
|
|
Expected: "811c7003375852fabd0d362e40e68607a12bdabae61a7d068fe5fdd1dbbf2a5d",
|
|
Name: "128",
|
|
}
|
|
benchmarkPrecompiled("02", t, bench)
|
|
}
|
|
|
|
// Benchmarks the sample inputs from the RIPEMD precompile.
|
|
func BenchmarkPrecompiledRipeMD(bench *testing.B) {
|
|
t := precompiledTest{
|
|
Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
|
|
Expected: "0000000000000000000000009215b8d9882ff46f0dfde6684d78e831467f65e6",
|
|
Name: "128",
|
|
}
|
|
benchmarkPrecompiled("03", t, bench)
|
|
}
|
|
|
|
// Benchmarks the sample inputs from the identiy precompile.
|
|
func BenchmarkPrecompiledIdentity(bench *testing.B) {
|
|
t := precompiledTest{
|
|
Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
|
|
Expected: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
|
|
Name: "128",
|
|
}
|
|
benchmarkPrecompiled("04", t, bench)
|
|
}
|
|
|
|
// Tests the sample inputs from the ModExp EIP 198.
|
|
func TestPrecompiledModExp(t *testing.T) { testJson("modexp", "05", t) }
|
|
func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) }
|
|
|
|
// Tests the sample inputs from the elliptic curve addition EIP 213.
|
|
func TestPrecompiledBn256Add(t *testing.T) { testJson("bn256Add", "06", t) }
|
|
func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) }
|
|
|
|
// Tests OOG
|
|
func TestPrecompiledModExpOOG(t *testing.T) {
|
|
modexpTests, err := loadJson("modexp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, test := range modexpTests {
|
|
testPrecompiledOOG("05", test, t)
|
|
}
|
|
}
|
|
|
|
// Tests the sample inputs from the elliptic curve scalar multiplication EIP 213.
|
|
func TestPrecompiledBn256ScalarMul(t *testing.T) { testJson("bn256ScalarMul", "07", t) }
|
|
func BenchmarkPrecompiledBn256ScalarMul(b *testing.B) { benchJson("bn256ScalarMul", "07", b) }
|
|
|
|
// Tests the sample inputs from the elliptic curve pairing check EIP 197.
|
|
func TestPrecompiledBn256Pairing(t *testing.T) { testJson("bn256Pairing", "08", t) }
|
|
func BenchmarkPrecompiledBn256Pairing(b *testing.B) { benchJson("bn256Pairing", "08", b) }
|
|
|
|
func TestPrecompiledBlake2F(t *testing.T) { testJson("blake2F", "09", t) }
|
|
func BenchmarkPrecompiledBlake2F(b *testing.B) { benchJson("blake2F", "09", b) }
|
|
|
|
func TestPrecompileBlake2FMalformedInput(t *testing.T) {
|
|
for _, test := range blake2FMalformedInputTests {
|
|
testPrecompiledFailure("09", test, t)
|
|
}
|
|
}
|
|
|
|
func TestPrecompiledEcrecover(t *testing.T) { testJson("ecRecover", "01", t) }
|
|
|
|
func testJson(name, addr string, t *testing.T) {
|
|
tests, err := loadJson(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, test := range tests {
|
|
testPrecompiled(addr, test, t)
|
|
}
|
|
}
|
|
|
|
func testJsonFail(name, addr string, t *testing.T) {
|
|
tests, err := loadJsonFail(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, test := range tests {
|
|
testPrecompiledFailure(addr, test, t)
|
|
}
|
|
}
|
|
|
|
func benchJson(name, addr string, b *testing.B) {
|
|
tests, err := loadJson(name)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
for _, test := range tests {
|
|
benchmarkPrecompiled(addr, test, b)
|
|
}
|
|
}
|
|
|
|
func TestPrecompiledBLS12381G1Add(t *testing.T) { testJson("blsG1Add", "0a", t) }
|
|
func TestPrecompiledBLS12381G1Mul(t *testing.T) { testJson("blsG1Mul", "0b", t) }
|
|
func TestPrecompiledBLS12381G1MultiExp(t *testing.T) { testJson("blsG1MultiExp", "0c", t) }
|
|
func TestPrecompiledBLS12381G2Add(t *testing.T) { testJson("blsG2Add", "0d", t) }
|
|
func TestPrecompiledBLS12381G2Mul(t *testing.T) { testJson("blsG2Mul", "0e", t) }
|
|
func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", "0f", t) }
|
|
func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) }
|
|
func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) }
|
|
func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) }
|
|
|
|
func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) }
|
|
func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) }
|
|
func BenchmarkPrecompiledBLS12381G1MultiExp(b *testing.B) { benchJson("blsG1MultiExp", "0c", b) }
|
|
func BenchmarkPrecompiledBLS12381G2Add(b *testing.B) { benchJson("blsG2Add", "0d", b) }
|
|
func BenchmarkPrecompiledBLS12381G2Mul(b *testing.B) { benchJson("blsG2Mul", "0e", b) }
|
|
func BenchmarkPrecompiledBLS12381G2MultiExp(b *testing.B) { benchJson("blsG2MultiExp", "0f", b) }
|
|
func BenchmarkPrecompiledBLS12381Pairing(b *testing.B) { benchJson("blsPairing", "10", b) }
|
|
func BenchmarkPrecompiledBLS12381MapG1(b *testing.B) { benchJson("blsMapG1", "11", b) }
|
|
func BenchmarkPrecompiledBLS12381MapG2(b *testing.B) { benchJson("blsMapG2", "12", b) }
|
|
|
|
// Failure tests
|
|
func TestPrecompiledBLS12381G1AddFail(t *testing.T) { testJsonFail("blsG1Add", "0a", t) }
|
|
func TestPrecompiledBLS12381G1MulFail(t *testing.T) { testJsonFail("blsG1Mul", "0b", t) }
|
|
func TestPrecompiledBLS12381G1MultiExpFail(t *testing.T) { testJsonFail("blsG1MultiExp", "0c", t) }
|
|
func TestPrecompiledBLS12381G2AddFail(t *testing.T) { testJsonFail("blsG2Add", "0d", t) }
|
|
func TestPrecompiledBLS12381G2MulFail(t *testing.T) { testJsonFail("blsG2Mul", "0e", t) }
|
|
func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJsonFail("blsG2MultiExp", "0f", t) }
|
|
func TestPrecompiledBLS12381PairingFail(t *testing.T) { testJsonFail("blsPairing", "10", t) }
|
|
func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG1", "11", t) }
|
|
func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "12", t) }
|
|
|
|
func loadJson(name string) ([]precompiledTest, error) {
|
|
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var testcases []precompiledTest
|
|
err = json.Unmarshal(data, &testcases)
|
|
return testcases, err
|
|
}
|
|
|
|
func loadJsonFail(name string) ([]precompiledFailureTest, error) {
|
|
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var testcases []precompiledFailureTest
|
|
err = json.Unmarshal(data, &testcases)
|
|
return testcases, err
|
|
}
|
|
|
|
// 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)
|
|
}
|