Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 0afe540

Browse files
Silas Davisseanyoung
Silas Davis
authored andcommitted
Implement cross-engine call dispatch for eWASM
- Implement the eWASM `call` syscall to support calling contracts - Wire up WASM VM as a Disptacher so it can call EVM and Natives (including Precompiles) as well as other WASM contracts. - To support 128-bit wide (and 256-bit wide EVM!) values use big.Int for Value and Gas. Note the Account model currently still uses uint64 so outside of the execution machinery we still throw an error if a balance would overflow uint64. This paves the way for relaxing this requirement and making account balances etc. - exec.CallData `Value` and `Gas` are now the big-endian big.Int bytes rather than uint64 to support the above. - EVM and WASM now share calling and some other code. EVM now more strictly throws errors that are not ExecutionReverted becuase it probably always should have and makes it more consistent with WASM. WASM uses panics to transmit its errors. If we have errors that should not halt execution (AKA 'non-trapping') we should probably explicitly encode this fact into the coded errors and act accordingly and consistenly in WASM and EVM Signed-off-by: Silas Davis <[email protected]>
1 parent 86b3b7b commit 0afe540

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1205
-894
lines changed

Diff for: binary/integer.go

+18
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ func SignExtend(x *big.Int, n uint) *big.Int {
6767
}
6868
}
6969

70+
// Reverse bytes in-place
71+
func reverse(bs []byte) []byte {
72+
n := len(bs) - 1
73+
for i := 0; i < len(bs)/2; i++ {
74+
bs[i], bs[n-i] = bs[n-i], bs[i]
75+
}
76+
return bs
77+
}
78+
79+
// Note: this function destructively reverses its input slice - pass a copy if the original is used elsewhere
80+
func BigIntFromLittleEndianBytes(bs []byte) *big.Int {
81+
return new(big.Int).SetBytes(reverse(bs))
82+
}
83+
84+
func BigIntToLittleEndianBytes(x *big.Int) []byte {
85+
return reverse(x.Bytes())
86+
}
87+
7088
func andMask(n uint) *big.Int {
7189
x := new(big.Int)
7290
return x.Sub(x.Lsh(big1, n), big1)

Diff for: binary/integer_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ func TestSignExtend(t *testing.T) {
133133
"0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 1000 0000 1101 0011 1001 0000")
134134
}
135135

136+
func TestLittleEndian(t *testing.T) {
137+
x, ok := new(big.Int).SetString("234890234579042368982348972347234789897", 10)
138+
require.True(t, ok)
139+
y := BigIntFromLittleEndianBytes(BigIntToLittleEndianBytes(x))
140+
require.Equal(t, x.Cmp(y), 0)
141+
}
142+
136143
func assertSignExtend(t *testing.T, extendedBits int, embeddedBits uint, inputString, expectedString string) bool {
137144
input := intFromString(t, extendedBits, inputString)
138145
expected := intFromString(t, extendedBits, expectedString)

Diff for: execution/config.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package execution
33
import (
44
"fmt"
55

6+
"github.com/hyperledger/burrow/execution/engine"
7+
68
"github.com/hyperledger/burrow/execution/evm"
79
)
810

@@ -45,7 +47,7 @@ func VMOptions(vmOptions evm.Options) func(*executor) {
4547
func (ec *ExecutionConfig) ExecutionOptions() ([]Option, error) {
4648
var exeOptions []Option
4749
vmOptions := evm.Options{
48-
MemoryProvider: evm.DefaultDynamicMemoryProvider,
50+
MemoryProvider: engine.DefaultDynamicMemoryProvider,
4951
CallStackMaxDepth: ec.CallStackMaxDepth,
5052
DataStackInitialCapacity: ec.DataStackInitialCapacity,
5153
DataStackMaxDepth: ec.DataStackMaxDepth,

Diff for: execution/contexts/call_context.go

+13-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package contexts
22

33
import (
44
"fmt"
5+
"math/big"
56

67
"github.com/hyperledger/burrow/acm"
78
"github.com/hyperledger/burrow/acm/acmstate"
@@ -10,7 +11,6 @@ import (
1011
"github.com/hyperledger/burrow/execution/errors"
1112
"github.com/hyperledger/burrow/execution/evm"
1213
"github.com/hyperledger/burrow/execution/exec"
13-
"github.com/hyperledger/burrow/execution/native"
1414
"github.com/hyperledger/burrow/execution/wasm"
1515
"github.com/hyperledger/burrow/logging"
1616
"github.com/hyperledger/burrow/logging/structure"
@@ -135,7 +135,7 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
135135
callee = crypto.NewContractAddress(caller, ctx.txe.TxHash)
136136
code = ctx.tx.Data
137137
wcode = ctx.tx.WASM
138-
err := native.CreateAccount(txCache, callee)
138+
err := engine.CreateAccount(txCache, callee)
139139
if err != nil {
140140
return err
141141
}
@@ -144,7 +144,7 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
144144
"init_code", code)
145145

146146
// store abis
147-
err = native.UpdateContractMeta(txCache, metaCache, callee, ctx.tx.ContractMeta)
147+
err = engine.UpdateContractMeta(txCache, metaCache, callee, ctx.tx.ContractMeta)
148148
if err != nil {
149149
return err
150150
}
@@ -184,19 +184,21 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
184184
var ret []byte
185185
var err error
186186
txHash := ctx.txe.Envelope.Tx.Hash()
187-
gas := ctx.tx.GasLimit
187+
gas := new(big.Int).SetUint64(ctx.tx.GasLimit)
188188

189189
params := engine.CallParams{
190190
Origin: caller,
191191
Caller: caller,
192192
Callee: callee,
193193
Input: ctx.tx.Data,
194-
Value: value,
195-
Gas: &gas,
194+
Value: *new(big.Int).SetUint64(value),
195+
Gas: gas,
196196
}
197197

198198
if len(wcode) != 0 {
199-
ret, err = wasm.RunWASM(txCache, params, wcode)
199+
// TODO: accept options
200+
vm := wasm.Default()
201+
ret, err = vm.Execute(txCache, ctx.Blockchain, ctx.txe, params, wcode)
200202
if err != nil {
201203
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
202204
ctx.Logger.InfoMsg("Error on WASM execution",
@@ -206,7 +208,7 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
206208
} else {
207209
ctx.Logger.TraceMsg("Successful execution")
208210
if createContract {
209-
err := native.InitWASMCode(txCache, callee, ret)
211+
err := engine.InitWASMCode(txCache, callee, ret)
210212
if err != nil {
211213
return err
212214
}
@@ -233,7 +235,7 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
233235
} else {
234236
ctx.Logger.TraceMsg("Successful execution")
235237
if createContract {
236-
err := native.InitEVMCode(txCache, callee, ret)
238+
err := engine.InitEVMCode(txCache, callee, ret)
237239
if err != nil {
238240
return err
239241
}
@@ -245,7 +247,8 @@ func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error
245247
}
246248
ctx.CallEvents(err)
247249
}
248-
ctx.txe.Return(ret, ctx.tx.GasLimit-gas)
250+
// Gas starts life as a uint64 and should only been reduced (used up) over a transaction so .Uint64() is safe
251+
ctx.txe.Return(ret, ctx.tx.GasLimit-gas.Uint64())
249252
// Create a receipt from the ret and whether it erred.
250253
ctx.Logger.TraceMsg("VM Call complete",
251254
"caller", caller,

Diff for: execution/native/account.go renamed to execution/engine/account.go

+17-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
package native
1+
package engine
22

33
import (
44
"bytes"
5+
"math/big"
56

67
"github.com/hyperledger/burrow/acm"
78
"github.com/hyperledger/burrow/acm/acmstate"
@@ -12,23 +13,6 @@ import (
1213
"golang.org/x/crypto/sha3"
1314
)
1415

15-
func CreateAccount(st acmstate.ReaderWriter, address crypto.Address) error {
16-
acc, err := st.GetAccount(address)
17-
if err != nil {
18-
return err
19-
}
20-
if acc != nil {
21-
if acc.NativeName != "" {
22-
return errors.Errorf(errors.Codes.ReservedAddress,
23-
"cannot create account at %v because that address is reserved for a native contract '%s'",
24-
address, acc.NativeName)
25-
}
26-
return errors.Errorf(errors.Codes.DuplicateAddress,
27-
"tried to create an account at an address that already exists: %v", address)
28-
}
29-
return st.UpdateAccount(&acm.Account{Address: address})
30-
}
31-
3216
func InitEVMCode(st acmstate.ReaderWriter, address crypto.Address, code []byte) error {
3317
return initEVMCode(st, address, nil, code)
3418
}
@@ -38,7 +22,7 @@ func InitChildCode(st acmstate.ReaderWriter, address crypto.Address, parent cryp
3822
}
3923

4024
func initEVMCode(st acmstate.ReaderWriter, address crypto.Address, parent *crypto.Address, code []byte) error {
41-
acc, err := mustAccount(st, address)
25+
acc, err := MustAccount(st, address)
4226
if err != nil {
4327
return err
4428
}
@@ -116,7 +100,7 @@ func codehashPermitted(codehash []byte, metamap []*acm.ContractMeta) bool {
116100
}
117101

118102
func InitWASMCode(st acmstate.ReaderWriter, address crypto.Address, code []byte) error {
119-
acc, err := mustAccount(st, address)
103+
acc, err := MustAccount(st, address)
120104
if err != nil {
121105
return err
122106
}
@@ -133,33 +117,37 @@ func InitWASMCode(st acmstate.ReaderWriter, address crypto.Address, code []byte)
133117
return st.UpdateAccount(acc)
134118
}
135119

136-
func Transfer(st acmstate.ReaderWriter, from, to crypto.Address, amount uint64) error {
137-
if amount == 0 {
120+
// TODO: consider pushing big.Int usage all the way to account balance
121+
func Transfer(st acmstate.ReaderWriter, from, to crypto.Address, amount *big.Int) error {
122+
if !amount.IsInt64() {
123+
return errors.Errorf(errors.Codes.IntegerOverflow, "transfer amount %v overflows int64", amount)
124+
}
125+
if amount.Sign() == 0 {
138126
return nil
139127
}
140-
acc, err := mustAccount(st, from)
128+
acc, err := MustAccount(st, from)
141129
if err != nil {
142130
return err
143131
}
144-
if acc.Balance < amount {
132+
if new(big.Int).SetUint64(acc.Balance).Cmp(amount) < 0 {
145133
return errors.Codes.InsufficientBalance
146134
}
147135
err = UpdateAccount(st, from, func(account *acm.Account) error {
148-
return account.SubtractFromBalance(amount)
136+
return account.SubtractFromBalance(amount.Uint64())
149137
})
150138
if err != nil {
151139
return err
152140
}
153141
return UpdateAccount(st, to, func(account *acm.Account) error {
154-
return account.AddToBalance(amount)
142+
return account.AddToBalance(amount.Uint64())
155143
})
156144
}
157145

158146
func UpdateContractMeta(st acmstate.ReaderWriter, metaSt acmstate.MetadataWriter, address crypto.Address, payloadMeta []*payload.ContractMeta) error {
159147
if len(payloadMeta) == 0 {
160148
return nil
161149
}
162-
acc, err := mustAccount(st, address)
150+
acc, err := MustAccount(st, address)
163151
if err != nil {
164152
return err
165153
}
@@ -194,7 +182,7 @@ func RemoveAccount(st acmstate.ReaderWriter, address crypto.Address) error {
194182
}
195183

196184
func UpdateAccount(st acmstate.ReaderWriter, address crypto.Address, updater func(acc *acm.Account) error) error {
197-
acc, err := mustAccount(st, address)
185+
acc, err := MustAccount(st, address)
198186
if err != nil {
199187
return err
200188
}
@@ -205,7 +193,7 @@ func UpdateAccount(st acmstate.ReaderWriter, address crypto.Address, updater fun
205193
return st.UpdateAccount(acc)
206194
}
207195

208-
func mustAccount(st acmstate.Reader, address crypto.Address) (*acm.Account, error) {
196+
func MustAccount(st acmstate.Reader, address crypto.Address) (*acm.Account, error) {
209197
acc, err := st.GetAccount(address)
210198
if err != nil {
211199
return nil, err

Diff for: execution/native/account_test.go renamed to execution/engine/account_test.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
package native
1+
package engine
22

33
import (
44
"testing"
55

66
"github.com/hyperledger/burrow/acm"
77
"github.com/hyperledger/burrow/acm/acmstate"
88
"github.com/hyperledger/burrow/crypto"
9-
"github.com/hyperledger/burrow/execution/engine"
109
"github.com/hyperledger/burrow/execution/errors"
1110
"github.com/stretchr/testify/assert"
1211
"github.com/stretchr/testify/require"
@@ -30,7 +29,7 @@ func TestState_CreateAccount(t *testing.T) {
3029

3130
func TestState_Sync(t *testing.T) {
3231
backend := acmstate.NewCache(acmstate.NewMemoryState())
33-
st := engine.NewCallFrame(backend)
32+
st := NewCallFrame(backend)
3433
address := AddressFromName("frogs")
3534

3635
err := CreateAccount(st, address)
@@ -46,7 +45,7 @@ func TestState_Sync(t *testing.T) {
4645
}
4746

4847
func TestState_NewCache(t *testing.T) {
49-
st := engine.NewCallFrame(acmstate.NewMemoryState())
48+
st := NewCallFrame(acmstate.NewMemoryState())
5049
address := AddressFromName("frogs")
5150

5251
cache, err := st.NewFrame()
@@ -64,7 +63,7 @@ func TestState_NewCache(t *testing.T) {
6463
err = cache.Sync()
6564
require.NoError(t, err)
6665

67-
acc, err = mustAccount(cache, address)
66+
acc, err = MustAccount(cache, address)
6867
require.NoError(t, err)
6968
assert.Equal(t, amt, acc.Balance)
7069

Diff for: execution/engine/accounts.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package engine
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hyperledger/burrow/acm"
7+
"github.com/hyperledger/burrow/acm/acmstate"
8+
"github.com/hyperledger/burrow/crypto"
9+
"github.com/hyperledger/burrow/execution/errors"
10+
"github.com/hyperledger/burrow/permission"
11+
)
12+
13+
type Maybe interface {
14+
PushError(err error) bool
15+
Error() error
16+
}
17+
18+
func GetAccount(st acmstate.Reader, m Maybe, address crypto.Address) *acm.Account {
19+
acc, err := st.GetAccount(address)
20+
if err != nil {
21+
m.PushError(err)
22+
return nil
23+
}
24+
return acc
25+
}
26+
27+
// Guaranteed to return a non-nil account, if the account does not exist returns a pointer to the zero-value of Account
28+
// and pushes an error.
29+
func MustGetAccount(st acmstate.Reader, m Maybe, address crypto.Address) *acm.Account {
30+
acc := GetAccount(st, m, address)
31+
if acc == nil {
32+
m.PushError(errors.Errorf(errors.Codes.NonExistentAccount, "account %v does not exist", address))
33+
return &acm.Account{}
34+
}
35+
return acc
36+
}
37+
38+
func EnsurePermission(callFrame *CallFrame, address crypto.Address, perm permission.PermFlag) error {
39+
hasPermission, err := HasPermission(callFrame, address, perm)
40+
if err != nil {
41+
return err
42+
} else if !hasPermission {
43+
return errors.PermissionDenied{
44+
Address: address,
45+
Perm: perm,
46+
}
47+
}
48+
return nil
49+
}
50+
51+
// CONTRACT: it is the duty of the contract writer to call known permissions
52+
// we do not convey if a permission is not set
53+
// (unlike in state/execution, where we guarantee HasPermission is called
54+
// on known permissions and panics else)
55+
// If the perm is not defined in the acc nor set by default in GlobalPermissions,
56+
// this function returns false.
57+
func HasPermission(st acmstate.Reader, address crypto.Address, perm permission.PermFlag) (bool, error) {
58+
acc, err := st.GetAccount(address)
59+
if err != nil {
60+
return false, err
61+
}
62+
if acc == nil {
63+
return false, fmt.Errorf("account %v does not exist", address)
64+
}
65+
globalPerms, err := acmstate.GlobalAccountPermissions(st)
66+
if err != nil {
67+
return false, err
68+
}
69+
perms := acc.Permissions.Base.Compose(globalPerms.Base)
70+
value, err := perms.Get(perm)
71+
if err != nil {
72+
return false, err
73+
}
74+
return value, nil
75+
}
76+
77+
func CreateAccount(st acmstate.ReaderWriter, address crypto.Address) error {
78+
acc, err := st.GetAccount(address)
79+
if err != nil {
80+
return err
81+
}
82+
if acc != nil {
83+
if acc.NativeName != "" {
84+
return errors.Errorf(errors.Codes.ReservedAddress,
85+
"cannot create account at %v because that address is reserved for a native contract '%s'",
86+
address, acc.NativeName)
87+
}
88+
return errors.Errorf(errors.Codes.DuplicateAddress,
89+
"tried to create an account at an address that already exists: %v", address)
90+
}
91+
return st.UpdateAccount(&acm.Account{Address: address})
92+
}
93+
94+
func AddressFromName(name string) (address crypto.Address) {
95+
hash := crypto.Keccak256([]byte(name))
96+
copy(address[:], hash[len(hash)-crypto.AddressLength:])
97+
return
98+
}

0 commit comments

Comments
 (0)