ewasm precompiles pass rinkeby sync

This commit is contained in:
Guillaume Ballet 2019-02-07 22:07:12 +01:00
parent b5c6111ad7
commit a098295149
9 changed files with 209 additions and 61 deletions

View file

@ -39,7 +39,7 @@ import (
// contract. // contract.
type PrecompiledContract interface { type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract Run(input []byte, contract *Contract) ([]byte, error) // Run runs the precompiled contract
} }
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
@ -67,21 +67,21 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
// PrecompiledContractsEWASM contains the default set of pre-compiled Ethereum // PrecompiledContractsEWASM contains the default set of pre-compiled Ethereum
// contracts used for Ethereum 1.x release. // contracts used for Ethereum 1.x release.
var PrecompiledContractsEWASM = map[common.Address]PrecompiledContract{ var PrecompiledContractsEWASM = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): newEWASMPrecompile(ewasmEcRecoverCode), common.BytesToAddress([]byte{1}): newEWASMPrecompile(ewasmEcrecoverCode, 1),
common.BytesToAddress([]byte{2}): newEWASMPrecompile(ewasmSha256HashCode), common.BytesToAddress([]byte{2}): newEWASMPrecompile(ewasmSha256HashCode, 2),
common.BytesToAddress([]byte{3}): newEWASMPrecompile(ewasmRipemd160hashCode), common.BytesToAddress([]byte{3}): newEWASMPrecompile(ewasmRipemd160hashCode, 3),
common.BytesToAddress([]byte{4}): newEWASMPrecompile(ewasmIdentityCode), common.BytesToAddress([]byte{4}): newEWASMPrecompile(ewasmIdentityCode, 4),
common.BytesToAddress([]byte{5}): newEWASMPrecompile(ewasmExpModCode), common.BytesToAddress([]byte{5}): newEWASMPrecompile(ewasmExpmodCode, 5),
common.BytesToAddress([]byte{6}): newEWASMPrecompile(ewasmEcAddCode), common.BytesToAddress([]byte{6}): newEWASMPrecompile(ewasmEcaddCode, 6),
common.BytesToAddress([]byte{7}): newEWASMPrecompile(ewasmEcMulCode), common.BytesToAddress([]byte{7}): newEWASMPrecompile(ewasmEcmulCode, 7),
common.BytesToAddress([]byte{8}): newEWASMPrecompile(ewasmEcPairingCode), common.BytesToAddress([]byte{8}): newEWASMPrecompile(ewasmEcpairingCode, 8),
} }
// RunPrecompiledContract runs and evaluates the output of a precompiled contract. // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input) gas := p.RequiredGas(input)
if contract.UseGas(gas) { if contract.UseGas(gas) {
return p.Run(input) return p.Run(input, contract)
} }
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -91,6 +91,8 @@ type ewasmPrecompile struct {
vm *exec.VM vm *exec.VM
contract *Contract contract *Contract
retData []byte retData []byte
idx uint32
input []byte
} }
// This is a subset of the functions available in the full EEI at // This is a subset of the functions available in the full EEI at
@ -142,17 +144,19 @@ func moduleResolver(name string, precompile *ewasmPrecompile) (*wasm.Module, err
{ {
Sig: &m.Types.Entries[2], Sig: &m.Types.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) { Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) {
if l > 0 {
// Unlike regular EEI functions, the gas is not charged at this // Unlike regular EEI functions, the gas is not charged at this
// time but I'm leaving that code here for future reference. // time but I'm leaving that code here for future reference.
// in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(l+31)>>5)) // in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(l+31)>>5))
p.WriteAt(precompile.contract.Input[d:d+l], int64(r)) p.WriteAt(precompile.input[d:d+l], int64(r))
}
}), }),
Body: &wasm.FunctionBody{}, Body: &wasm.FunctionBody{},
}, },
{ {
Sig: &m.Types.Entries[3], Sig: &m.Types.Entries[3],
Host: reflect.ValueOf(func(p *exec.Process) int32 { Host: reflect.ValueOf(func(p *exec.Process) int32 {
return int32(len(precompile.contract.Input)) return int32(len(precompile.input))
}), }),
Body: &wasm.FunctionBody{}, Body: &wasm.FunctionBody{},
}, },
@ -195,17 +199,24 @@ func moduleResolver(name string, precompile *ewasmPrecompile) (*wasm.Module, err
func newEWASMPrecompile(code []byte) *ewasmPrecompile { func newEWASMPrecompile(code []byte) *ewasmPrecompile {
ret := &ewasmPrecompile{} ret := &ewasmPrecompile{}
module, err := wasm.ReadModule(bytes.NewReader(code), func(s string)(*wasm.Module, error) { module, err := wasm.ReadModule(bytes.NewReader(code), func(s string) (*wasm.Module, error) {
return moduleResolver(s, ret) return moduleResolver(s, ret)
}) })
if err != nil { if err != nil {
panic(fmt.Sprintf("Could not read precompile module: %v", err)) panic(fmt.Sprintf("Could not read precompile module: %v", err))
} }
for name, export := range module.Export.Entries {
if name == "main" && export.Kind == wasm.ExternalFunction {
ret.idx = export.Index
}
}
vm, err := exec.NewVM(module) vm, err := exec.NewVM(module)
if err != nil { if err != nil {
panic("Could not create precompile VM") panic("Could not create precompile VM")
} }
vm.RecoverPanic = true
ret.vm = vm ret.vm = vm
return ret return ret
@ -215,7 +226,8 @@ func (c *ewasmPrecompile) RequiredGas(input []byte) uint64 {
return 0 return 0
} }
func (c *ewasmPrecompile) Run(input []byte) ([]byte, error) { func (c *ewasmPrecompile) Run(input []byte, contract *Contract) ([]byte, error) {
c.vm.Restart()
mem := c.vm.Memory() mem := c.vm.Memory()
/* Copy input into memory */ /* Copy input into memory */
@ -223,8 +235,15 @@ func (c *ewasmPrecompile) Run(input []byte) ([]byte, error) {
return nil, fmt.Errorf("input size (%d) is greater than available memory (%d)", len(input), len(mem)) return nil, fmt.Errorf("input size (%d) is greater than available memory (%d)", len(input), len(mem))
} }
c.input = input
c.contract = contract
defer func() {
c.input = nil
c.contract = nil
}()
/* Run the contract */ /* Run the contract */
_, err := c.vm.ExecCode(0) _, err := c.vm.ExecCode(int64(c.idx))
if err == nil { if err == nil {
return c.retData, nil return c.retData, nil
} }
@ -238,7 +257,7 @@ func (c *ecrecover) RequiredGas(input []byte) uint64 {
return params.EcrecoverGas return params.EcrecoverGas
} }
func (c *ecrecover) Run(input []byte) ([]byte, error) { func (c *ecrecover) Run(input []byte, contract *Contract) ([]byte, error) {
const ecRecoverInputLength = 128 const ecRecoverInputLength = 128
input = common.RightPadBytes(input, ecRecoverInputLength) input = common.RightPadBytes(input, ecRecoverInputLength)
@ -253,6 +272,7 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) {
if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) {
return nil, nil return nil, nil
} }
// v needs to be at the end for libsecp256k1 // v needs to be at the end for libsecp256k1
pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v)) pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v))
// make sure the public key is a valid one // make sure the public key is a valid one
@ -274,7 +294,7 @@ type sha256hash struct{}
func (c *sha256hash) RequiredGas(input []byte) uint64 { func (c *sha256hash) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas
} }
func (c *sha256hash) Run(input []byte) ([]byte, error) { func (c *sha256hash) Run(input []byte, contract *Contract) ([]byte, error) {
h := sha256.Sum256(input) h := sha256.Sum256(input)
return h[:], nil return h[:], nil
} }
@ -289,7 +309,7 @@ type ripemd160hash struct{}
func (c *ripemd160hash) RequiredGas(input []byte) uint64 { func (c *ripemd160hash) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas
} }
func (c *ripemd160hash) Run(input []byte) ([]byte, error) { func (c *ripemd160hash) Run(input []byte, contract *Contract) ([]byte, error) {
ripemd := ripemd160.New() ripemd := ripemd160.New()
ripemd.Write(input) ripemd.Write(input)
return common.LeftPadBytes(ripemd.Sum(nil), 32), nil return common.LeftPadBytes(ripemd.Sum(nil), 32), nil
@ -305,7 +325,7 @@ type dataCopy struct{}
func (c *dataCopy) RequiredGas(input []byte) uint64 { func (c *dataCopy) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas
} }
func (c *dataCopy) Run(in []byte) ([]byte, error) { func (c *dataCopy) Run(in []byte, contract *Contract) ([]byte, error) {
return in, nil return in, nil
} }
@ -386,7 +406,7 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
return gas.Uint64() return gas.Uint64()
} }
func (c *bigModExp) Run(input []byte) ([]byte, error) { func (c *bigModExp) Run(input []byte, contract *Contract) ([]byte, error) {
var ( var (
baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64()
expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64()
@ -442,7 +462,7 @@ func (c *bn256Add) RequiredGas(input []byte) uint64 {
return params.Bn256AddGas return params.Bn256AddGas
} }
func (c *bn256Add) Run(input []byte) ([]byte, error) { func (c *bn256Add) Run(input []byte, contract *Contract) ([]byte, error) {
x, err := newCurvePoint(getData(input, 0, 64)) x, err := newCurvePoint(getData(input, 0, 64))
if err != nil { if err != nil {
return nil, err return nil, err
@ -464,7 +484,7 @@ func (c *bn256ScalarMul) RequiredGas(input []byte) uint64 {
return params.Bn256ScalarMulGas return params.Bn256ScalarMulGas
} }
func (c *bn256ScalarMul) Run(input []byte) ([]byte, error) { func (c *bn256ScalarMul) Run(input []byte, contract *Contract) ([]byte, error) {
p, err := newCurvePoint(getData(input, 0, 64)) p, err := newCurvePoint(getData(input, 0, 64))
if err != nil { if err != nil {
return nil, err return nil, err
@ -493,7 +513,7 @@ func (c *bn256Pairing) RequiredGas(input []byte) uint64 {
return params.Bn256PairingBaseGas + uint64(len(input)/192)*params.Bn256PairingPerPointGas return params.Bn256PairingBaseGas + uint64(len(input)/192)*params.Bn256PairingPerPointGas
} }
func (c *bn256Pairing) Run(input []byte) ([]byte, error) { func (c *bn256Pairing) Run(input []byte, contract *Contract) ([]byte, error) {
// Handle some corner cases cheaply // Handle some corner cases cheaply
if len(input)%192 > 0 { if len(input)%192 > 0 {
return nil, errBadPairingInput return nil, errBadPairingInput

View file

@ -17,6 +17,7 @@
package vm package vm
import ( import (
"bytes"
"fmt" "fmt"
"math/big" "math/big"
"testing" "testing"
@ -350,11 +351,138 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
}) })
} }
var (
ecRecoverAddr = common.HexToAddress("01")
ripeMdAddr = common.HexToAddress("03")
expModAddr = common.HexToAddress("05")
)
type ewasmPrecompileTest struct {
name string
input []byte
err error
addr common.Address
expected []byte
}
var ewasmPrecompileTests = []ewasmPrecompileTest{
{
name: "EmptyEcRecover",
input: []byte{},
addr: ecRecoverAddr,
expected: []byte{},
},
{
name: "SimpleEcRecover",
input: common.Hex2Bytes("38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02"),
addr: ecRecoverAddr,
expected: common.Hex2Bytes("000000000000000000000000ceaccac640adf55b2028469bd36ba501f28b699d"),
},
{
name: "RipeMdPadding",
input: []byte{178, 121, 24, 45, 153, 230, 87, 3, 240, 7, 110, 72, 18, 101, 58, 171, 133, 252, 160, 240, 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, 11, 136, 23, 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, 89, 155, 67, 62},
addr: ecRecoverAddr,
expected: []byte{},
},
{
name: "EmptyModExp",
input: []byte{},
addr: ecRecoverAddr,
expected: []byte{},
},
{
name: "SimpleModExp",
input: []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, 128, 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, 32, 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, 128, 105, 244, 148, 17, 230, 162, 38, 226, 21, 123, 99, 173, 92, 154, 225, 98, 221, 178, 13, 54, 142, 147, 227, 219, 150, 131, 6, 199, 20, 249, 189, 163, 10, 56, 142, 74, 160, 10, 23, 22, 133, 219, 196, 237, 209, 230, 181, 148, 170, 136, 152, 109, 145, 62, 113, 35, 177, 70, 90, 254, 242, 241, 37, 193, 137, 34, 80, 63, 201, 219, 98, 68, 242, 229, 107, 41, 194, 61, 241, 15, 186, 12, 61, 121, 166, 99, 226, 51, 245, 19, 98, 0, 231, 66, 220, 25, 113, 116, 46, 94, 215, 214, 68, 212, 24, 96, 201, 47, 67, 161, 233, 119, 20, 115, 28, 119, 60, 46, 206, 8, 59, 243, 245, 178, 177, 123, 131, 65, 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, 1, 0, 1, 132, 63, 73, 203, 78, 27, 50, 102, 66, 138, 185, 34, 220, 161, 134, 52, 173, 16, 83, 166, 181, 82, 151, 245, 166, 158, 136, 110, 61, 191, 138, 213, 72, 170, 180, 26, 170, 159, 62, 89, 236, 155, 217, 242, 130, 160, 23, 193, 205, 210, 69, 52, 141, 166, 173, 203, 220, 93, 101, 86, 158, 107, 181, 184, 207, 171, 57, 103, 67, 243, 102, 12, 159, 44, 122, 105, 76, 152, 164, 7, 96, 175, 192, 144, 139, 76, 117, 90, 26, 188, 138, 149, 200, 175, 108, 201, 17, 211, 247, 177, 92, 214, 65, 243, 35, 10, 106, 79, 218, 176, 18, 106, 2, 71, 3, 174, 218, 178, 16, 244, 244, 89, 165, 195, 179, 247, 120, 47,
},
addr: expModAddr,
expected: []byte{0, 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 48, 49, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 5, 0, 4, 32, 152, 203, 93, 245, 45, 169, 18, 4, 171, 193, 131, 35, 108, 212, 190, 245, 181, 170, 134, 108, 120, 170, 237, 179, 241, 226, 128, 212, 149, 52, 51, 151},
},
{
name: "IncompleteInputModExp",
input: []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, 1},
addr: expModAddr,
expected: []byte{},
},
{
name: "PaddingExpMod",
input: []byte{32},
addr: expModAddr,
expected: []byte{},
},
}
func testEwasmPrecompiled(test ewasmPrecompileTest, t *testing.T) {
ref := PrecompiledContractsByzantium[test.addr]
p := PrecompiledContractsEWASM[test.addr]
input := test.input
contract := NewContract(AccountRef(common.HexToAddress("1337")),
nil, new(big.Int), ref.RequiredGas(input))
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(t *testing.T) {
res, err := p.Run(input, contract)
refRes, refErr := ref.Run(input, contract)
if err != refErr {
t.Fatalf("Expected error %v, got %v", test.err, err)
} else {
if bytes.Compare(refRes, res) != 0 {
t.Fatalf("Expected result to be %v, got %v", refRes, res)
}
}
})
}
func TestEwasmPrecompiles(t *testing.T) {
for _, test := range ewasmPrecompileTests {
testEwasmPrecompiled(test, t)
}
}
func TestExpModEwasmPadding(t *testing.T) {
ecRecoverAddr := common.HexToAddress("05")
p := PrecompiledContractsEWASM[ecRecoverAddr]
input := []byte{32}
reqGas := PrecompiledContractsByzantium[ecRecoverAddr].RequiredGas(input)
contract := NewContract(AccountRef(common.HexToAddress("1337")),
nil, new(big.Int), reqGas)
fmt.Println(contract.Gas)
res, err := RunPrecompiledContract(p, input, contract)
if err != nil {
t.Errorf("%v", err)
}
expected, _ := PrecompiledContractsByzantium[ecRecoverAddr].Run(input, contract)
fmt.Println(expected, res)
if bytes.Compare(res, expected) != 0 {
t.Errorf("Expected %v, got %v", expected, res)
}
if bytes.Compare(res, []byte{}) == 0 {
t.Error("Result should not be empty")
}
}
func TestOutOfBoundsEwasm(t *testing.T) {
for i := 0; i < 4000; i++ {
p := PrecompiledContractsEWASM[common.HexToAddress("02")]
input := []byte{1, 4, 8, 156, 149, 204, 68, 56, 9, 182, 75, 13, 33, 153, 31, 97, 58, 45, 60, 242, 214, 14, 44, 150, 23, 112, 129, 74, 183, 21, 241, 39, 61, 172, 144, 133, 93, 5, 217, 174, 157, 25, 62, 127, 95, 122, 213, 55, 21, 235, 57, 238, 102, 222, 98, 244, 67, 189, 66, 161, 183, 59, 94, 234, 248, 227, 253, 148, 250, 113, 188, 11, 161, 13, 57, 212, 100, 208, 216, 244, 101, 239, 238, 240, 162, 118, 78, 56, 135, 252, 201, 223, 65, 222, 210, 15, 80, 92}
reqGas := PrecompiledContractsByzantium[common.HexToAddress("02")].RequiredGas(input)
contract := NewContract(AccountRef(common.HexToAddress("1337")),
nil, new(big.Int), reqGas)
fmt.Println("req gas:", reqGas)
res, err := RunPrecompiledContract(p, input, contract)
if err != nil {
t.Errorf("%v", err)
}
expected, _ := PrecompiledContractsByzantium[common.HexToAddress("02")].Run(input, contract)
if bytes.Compare(res, expected) != 0 {
t.Errorf("Expected %v, got %v", expected, res)
}
}
}
func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
if test.noBenchmark { if test.noBenchmark {
return return
} }
p := PrecompiledContractsByzantium[common.HexToAddress(addr)] p := PrecompiledContractsEWASM[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input) in := common.Hex2Bytes(test.input)
reqGas := p.RequiredGas(in) reqGas := p.RequiredGas(in)
contract := NewContract(AccountRef(common.HexToAddress("1337")), contract := NewContract(AccountRef(common.HexToAddress("1337")),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long