Merge branch 'master' into miner

This commit is contained in:
Maran 2014-03-24 10:24:39 +01:00
commit 97786d03d5
18 changed files with 796 additions and 521 deletions

View File

@ -6,7 +6,7 @@ Ethereum
Ethereum Go Development package (C) Jeffrey Wilcke
Ethereum is currently in its testing phase. The current state is "Proof
of Concept 3". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)).
of Concept 3.5". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)).
Ethereum Go is split up in several sub packages Please refer to each
individual package for more information.

View File

@ -6,23 +6,39 @@ import (
)
type Account struct {
Amount *big.Int
Nonce uint64
address []byte
Amount *big.Int
Nonce uint64
}
func NewAccount(amount *big.Int) *Account {
return &Account{Amount: amount, Nonce: 0}
func NewAccount(address []byte, amount *big.Int) *Account {
return &Account{address, amount, 0}
}
func NewAccountFromData(data []byte) *Account {
address := &Account{}
address.RlpDecode(data)
func NewAccountFromData(address, data []byte) *Account {
account := &Account{address: address}
account.RlpDecode(data)
return address
return account
}
func (a *Account) AddFee(fee *big.Int) {
a.Amount.Add(a.Amount, fee)
a.AddFunds(fee)
}
func (a *Account) AddFunds(funds *big.Int) {
a.Amount.Add(a.Amount, funds)
}
func (a *Account) Address() []byte {
return a.address
}
// Implements Callee
func (a *Account) ReturnGas(value *big.Int, state *State) {
// Return the value back to the sender
a.AddFunds(value)
state.UpdateAccount(a.address, a)
}
func (a *Account) RlpEncode() []byte {

View File

@ -142,7 +142,7 @@ func (block *Block) PayFee(addr []byte, fee *big.Int) bool {
data := block.state.trie.Get(string(block.Coinbase))
// Get the ether (Coinbase) and add the fee (gief fee to miner)
ether := NewAccountFromData([]byte(data))
ether := NewAccountFromData(block.Coinbase, []byte(data))
base = new(big.Int)
ether.Amount = base.Add(ether.Amount, fee)

View File

@ -1,5 +1,6 @@
package ethchain
/*
import (
_ "fmt"
"github.com/ethereum/eth-go/ethdb"
@ -14,9 +15,10 @@ func TestVm(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
ethutil.Config.Db = db
bm := NewBlockManager(nil)
bm := NewStateManager(nil)
block := bm.bc.genesisBlock
bm.Prepare(block.State(), block.State())
script := Compile([]string{
"PUSH",
"1",
@ -31,3 +33,4 @@ func TestVm(t *testing.T) {
tx2.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
bm.ApplyTransactions(block, []*Transaction{tx2})
}
*/

90
ethchain/closure.go Normal file
View File

@ -0,0 +1,90 @@
package ethchain
// TODO Re write VM to use values instead of big integers?
import (
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type Callee interface {
ReturnGas(*big.Int, *State)
Address() []byte
}
type ClosureBody interface {
Callee
ethutil.RlpEncodable
GetMem(*big.Int) *ethutil.Value
SetMem(*big.Int, *ethutil.Value)
}
// Basic inline closure object which implement the 'closure' interface
type Closure struct {
callee Callee
object ClosureBody
State *State
Gas *big.Int
Value *big.Int
Args []byte
}
// Create a new closure for the given data items
func NewClosure(callee Callee, object ClosureBody, state *State, gas, val *big.Int) *Closure {
return &Closure{callee, object, state, gas, val, nil}
}
// Retuns the x element in data slice
func (c *Closure) GetMem(x *big.Int) *ethutil.Value {
m := c.object.GetMem(x)
if m == nil {
return ethutil.EmptyValue()
}
return m
}
func (c *Closure) SetMem(x *big.Int, val *ethutil.Value) {
c.object.SetMem(x, val)
}
func (c *Closure) Address() []byte {
return c.object.Address()
}
func (c *Closure) Call(vm *Vm, args []byte) []byte {
c.Args = args
return vm.RunClosure(c)
}
func (c *Closure) Return(ret []byte) []byte {
// Return the remaining gas to the callee
// If no callee is present return it to
// the origin (i.e. contract or tx)
if c.callee != nil {
c.callee.ReturnGas(c.Gas, c.State)
} else {
c.object.ReturnGas(c.Gas, c.State)
// TODO incase it's a POST contract we gotta serialise the contract again.
// But it's not yet defined
}
return ret
}
// Implement the Callee interface
func (c *Closure) ReturnGas(gas *big.Int, state *State) {
// Return the gas to the closure
c.Gas.Add(c.Gas, gas)
}
func (c *Closure) Object() ClosureBody {
return c.object
}
func (c *Closure) Callee() Callee {
return c.callee
}

View File

@ -9,26 +9,22 @@ type Contract struct {
Amount *big.Int
Nonce uint64
//state *ethutil.Trie
state *State
state *State
address []byte
}
func NewContract(Amount *big.Int, root []byte) *Contract {
contract := &Contract{Amount: Amount, Nonce: 0}
func NewContract(address []byte, Amount *big.Int, root []byte) *Contract {
contract := &Contract{address: address, Amount: Amount, Nonce: 0}
contract.state = NewState(ethutil.NewTrie(ethutil.Config.Db, string(root)))
return contract
}
func (c *Contract) RlpEncode() []byte {
return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root})
}
func NewContractFromBytes(address, data []byte) *Contract {
contract := &Contract{address: address}
contract.RlpDecode(data)
func (c *Contract) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
c.Amount = decoder.Get(0).BigInt()
c.Nonce = decoder.Get(1).Uint()
c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
return contract
}
func (c *Contract) Addr(addr []byte) *ethutil.Value {
@ -43,19 +39,45 @@ func (c *Contract) State() *State {
return c.state
}
func (c *Contract) GetMem(num int) *ethutil.Value {
nb := ethutil.BigToBytes(big.NewInt(int64(num)), 256)
func (c *Contract) GetMem(num *big.Int) *ethutil.Value {
nb := ethutil.BigToBytes(num, 256)
return c.Addr(nb)
}
func (c *Contract) SetMem(num *big.Int, val *ethutil.Value) {
addr := ethutil.BigToBytes(num, 256)
c.state.trie.Update(string(addr), string(val.Encode()))
}
// Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *Contract) ReturnGas(val *big.Int, state *State) {
c.Amount.Add(c.Amount, val)
}
func (c *Contract) Address() []byte {
return c.address
}
func (c *Contract) RlpEncode() []byte {
return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root})
}
func (c *Contract) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
c.Amount = decoder.Get(0).BigInt()
c.Nonce = decoder.Get(1).Uint()
c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
}
func MakeContract(tx *Transaction, state *State) *Contract {
// Create contract if there's no recipient
if tx.IsContract() {
addr := tx.Hash()[12:]
value := tx.Value
contract := NewContract(value, []byte(""))
contract := NewContract(addr, value, []byte(""))
state.trie.Update(string(addr), string(contract.RlpEncode()))
for i, val := range tx.Data {
if len(val) > 0 {

View File

@ -2,6 +2,7 @@ package ethchain
import (
"fmt"
_ "github.com/ethereum/eth-go/ethutil"
"math/big"
)
@ -9,111 +10,142 @@ type OpCode int
// Op codes
const (
oSTOP = 0x00
oADD = 0x01
oMUL = 0x02
oSUB = 0x03
oDIV = 0x04
oSDIV = 0x05
oMOD = 0x06
oSMOD = 0x07
oEXP = 0x08
oNEG = 0x09
oLT = 0x0a
oLE = 0x0b
oGT = 0x0c
oGE = 0x0d
oEQ = 0x0e
oNOT = 0x0f
oMYADDRESS = 0x10
oTXSENDER = 0x11
oTXVALUE = 0x12
oTXDATAN = 0x13
oTXDATA = 0x14
oBLK_PREVHASH = 0x15
oBLK_COINBASE = 0x16
oBLK_TIMESTAMP = 0x17
oBLK_NUMBER = 0x18
oBLK_DIFFICULTY = 0x19
oBLK_NONCE = 0x1a
oBASEFEE = 0x1b
oSHA256 = 0x20
oRIPEMD160 = 0x21
oECMUL = 0x22
oECADD = 0x23
oECSIGN = 0x24
oECRECOVER = 0x25
oECVALID = 0x26
oSHA3 = 0x27
oPUSH = 0x30
oPOP = 0x31
oDUP = 0x32
oSWAP = 0x33
oMLOAD = 0x34
oMSTORE = 0x35
oSLOAD = 0x36
oSSTORE = 0x37
oJMP = 0x38
oJMPI = 0x39
oIND = 0x3a
oEXTRO = 0x3b
oBALANCE = 0x3c
oMKTX = 0x3d
oSUICIDE = 0x3f
// 0x0 range - arithmetic ops
oSTOP = 0x00
oADD = 0x01
oMUL = 0x02
oSUB = 0x03
oDIV = 0x04
oSDIV = 0x05
oMOD = 0x06
oSMOD = 0x07
oEXP = 0x08
oNEG = 0x09
oLT = 0x0a
oGT = 0x0b
oEQ = 0x0c
oNOT = 0x0d
// 0x10 range - bit ops
oAND = 0x10
oOR = 0x11
oXOR = 0x12
oBYTE = 0x13
// 0x20 range - crypto
oSHA3 = 0x20
// 0x30 range - closure state
oADDRESS = 0x30
oBALANCE = 0x31
oORIGIN = 0x32
oCALLER = 0x33
oCALLVALUE = 0x34
oCALLDATA = 0x35
oCALLDATASIZE = 0x36
oGASPRICE = 0x37
// 0x40 range - block operations
oPREVHASH = 0x40
oCOINBASE = 0x41
oTIMESTAMP = 0x42
oNUMBER = 0x43
oDIFFICULTY = 0x44
oGASLIMIT = 0x45
// 0x50 range - 'storage' and execution
oPUSH = 0x50
oPOP = 0x51
oDUP = 0x52
oSWAP = 0x53
oMLOAD = 0x54
oMSTORE = 0x55
oMSTORE8 = 0x56
oSLOAD = 0x57
oSSTORE = 0x58
oJUMP = 0x59
oJUMPI = 0x5a
oPC = 0x5b
oMSIZE = 0x5c
// 0x60 range - closures
oCREATE = 0x60
oCALL = 0x61
oRETURN = 0x62
// 0x70 range - other
oLOG = 0x70 // XXX Unofficial
oSUICIDE = 0x7f
)
// Since the opcodes aren't all in order we can't use a regular slice
var opCodeToString = map[OpCode]string{
oSTOP: "STOP",
oADD: "ADD",
oMUL: "MUL",
oSUB: "SUB",
oDIV: "DIV",
oSDIV: "SDIV",
oMOD: "MOD",
oSMOD: "SMOD",
oEXP: "EXP",
oNEG: "NEG",
oLT: "LT",
oLE: "LE",
oGT: "GT",
oGE: "GE",
oEQ: "EQ",
oNOT: "NOT",
oMYADDRESS: "MYADDRESS",
oTXSENDER: "TXSENDER",
oTXVALUE: "TXVALUE",
oTXDATAN: "TXDATAN",
oTXDATA: "TXDATA",
oBLK_PREVHASH: "BLK_PREVHASH",
oBLK_COINBASE: "BLK_COINBASE",
oBLK_TIMESTAMP: "BLK_TIMESTAMP",
oBLK_NUMBER: "BLK_NUMBER",
oBLK_DIFFICULTY: "BLK_DIFFICULTY",
oBASEFEE: "BASEFEE",
oSHA256: "SHA256",
oRIPEMD160: "RIPEMD160",
oECMUL: "ECMUL",
oECADD: "ECADD",
oECSIGN: "ECSIGN",
oECRECOVER: "ECRECOVER",
oECVALID: "ECVALID",
oSHA3: "SHA3",
oPUSH: "PUSH",
oPOP: "POP",
oDUP: "DUP",
oSWAP: "SWAP",
oMLOAD: "MLOAD",
oMSTORE: "MSTORE",
oSLOAD: "SLOAD",
oSSTORE: "SSTORE",
oJMP: "JMP",
oJMPI: "JMPI",
oIND: "IND",
oEXTRO: "EXTRO",
oBALANCE: "BALANCE",
oMKTX: "MKTX",
oSUICIDE: "SUICIDE",
// 0x0 range - arithmetic ops
oSTOP: "STOP",
oADD: "ADD",
oMUL: "MUL",
oSUB: "SUB",
oDIV: "DIV",
oSDIV: "SDIV",
oMOD: "MOD",
oSMOD: "SMOD",
oEXP: "EXP",
oNEG: "NEG",
oLT: "LT",
oGT: "GT",
oEQ: "EQ",
oNOT: "NOT",
// 0x10 range - bit ops
oAND: "AND",
oOR: "OR",
oXOR: "XOR",
oBYTE: "BYTE",
// 0x20 range - crypto
oSHA3: "SHA3",
// 0x30 range - closure state
oADDRESS: "ADDRESS",
oBALANCE: "BALANCE",
oORIGIN: "ORIGIN",
oCALLER: "CALLER",
oCALLVALUE: "CALLVALUE",
oCALLDATA: "CALLDATA",
oCALLDATASIZE: "CALLDATASIZE",
oGASPRICE: "TXGASPRICE",
// 0x40 range - block operations
oPREVHASH: "PREVHASH",
oCOINBASE: "COINBASE",
oTIMESTAMP: "TIMESTAMP",
oNUMBER: "NUMBER",
oDIFFICULTY: "DIFFICULTY",
oGASLIMIT: "GASLIMIT",
// 0x50 range - 'storage' and execution
oPUSH: "PUSH",
oPOP: "POP",
oDUP: "DUP",
oSWAP: "SWAP",
oMLOAD: "MLOAD",
oMSTORE: "MSTORE",
oMSTORE8: "MSTORE8",
oSLOAD: "SLOAD",
oSSTORE: "SSTORE",
oJUMP: "JUMP",
oJUMPI: "JUMPI",
oPC: "PC",
oMSIZE: "MSIZE",
// 0x60 range - closures
oCREATE: "CREATE",
oCALL: "CALL",
oRETURN: "RETURN",
// 0x70 range - other
oLOG: "LOG",
oSUICIDE: "SUICIDE",
}
func (o OpCode) String() string {
@ -141,35 +173,27 @@ func NewStack() *Stack {
}
func (st *Stack) Pop() *big.Int {
s := len(st.data)
str := st.data[s-1]
st.data = st.data[:s-1]
str := st.data[0]
st.data = st.data[1:]
return str
}
func (st *Stack) Popn() (*big.Int, *big.Int) {
s := len(st.data)
ints := st.data[s-2:]
st.data = st.data[:s-2]
ints := st.data[:2]
st.data = st.data[2:]
return ints[0], ints[1]
}
func (st *Stack) Peek() *big.Int {
s := len(st.data)
str := st.data[s-1]
str := st.data[0]
return str
}
func (st *Stack) Peekn() (*big.Int, *big.Int) {
s := len(st.data)
ints := st.data[s-2:]
ints := st.data[:2]
return ints[0], ints[1]
}
@ -188,3 +212,45 @@ func (st *Stack) Print() {
}
fmt.Println("#############")
}
type Memory struct {
store []byte
}
func (m *Memory) Set(offset, size int64, value []byte) {
totSize := offset + size
lenSize := int64(len(m.store) - 1)
if totSize > lenSize {
// Calculate the diff between the sizes
diff := totSize - lenSize
if diff > 0 {
// Create a new empty slice and append it
newSlice := make([]byte, diff-1)
// Resize slice
m.store = append(m.store, newSlice...)
}
}
copy(m.store[offset:offset+size], value)
}
func (m *Memory) Get(offset, size int64) []byte {
return m.store[offset : offset+size]
}
func (m *Memory) Len() int {
return len(m.store)
}
func (m *Memory) Print() {
fmt.Println("### MEM ###")
if len(m.store) > 0 {
addr := 0
for i := 0; i+32 < len(m.store); i += 32 {
fmt.Printf("%03d %v\n", addr, m.store[i:i+32])
addr++
}
} else {
fmt.Println("-- empty --")
}
fmt.Println("###########")
}

View File

@ -63,8 +63,7 @@ func (s *State) GetContract(addr []byte) *Contract {
}
// build contract
contract := &Contract{}
contract.RlpDecode([]byte(data))
contract := NewContractFromBytes(addr, []byte(data))
// Check if there's a cached state for this contract
cachedState := s.states[string(addr)]
@ -78,27 +77,19 @@ func (s *State) GetContract(addr []byte) *Contract {
return contract
}
func (s *State) UpdateContract(addr []byte, contract *Contract) {
func (s *State) UpdateContract(contract *Contract) {
addr := contract.Address()
s.states[string(addr)] = contract.state
s.trie.Update(string(addr), string(contract.RlpEncode()))
}
func Compile(code []string) (script []string) {
script = make([]string, len(code))
for i, val := range code {
instr, _ := ethutil.CompileInstr(val)
script[i] = string(instr)
}
return
}
func (s *State) GetAccount(addr []byte) (account *Account) {
data := s.trie.Get(string(addr))
if data == "" {
account = NewAccount(big.NewInt(0))
account = NewAccount(addr, big.NewInt(0))
} else {
account = NewAccountFromData([]byte(data))
account = NewAccountFromData(addr, []byte(data))
}
return
@ -153,3 +144,35 @@ func (s *State) Get(key []byte) (*ethutil.Value, ObjType) {
return val, typ
}
func (s *State) Put(key, object []byte) {
s.trie.Update(string(key), string(object))
}
func (s *State) Root() interface{} {
return s.trie.Root
}
// Script compilation functions
// Compiles strings to machine code
func Compile(code []string) (script []string) {
script = make([]string, len(code))
for i, val := range code {
instr, _ := ethutil.CompileInstr(val)
script[i] = string(instr)
}
return
}
func CompileToValues(code []string) (script []*ethutil.Value) {
script = make([]*ethutil.Value, len(code))
for i, val := range code {
instr, _ := ethutil.CompileInstr(val)
script[i] = ethutil.NewValue(instr)
}
return
}

View File

@ -308,18 +308,17 @@ func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, blo
}
}()
*/
vm := &Vm{}
//vm.Process(contract, block.state, RuntimeVars{
vm.Process(contract, sm.procState, RuntimeVars{
address: tx.Hash()[12:],
caller := sm.procState.GetAccount(tx.Sender())
closure := NewClosure(caller, contract, sm.procState, tx.Gas, tx.Value)
vm := NewVm(sm.procState, RuntimeVars{
origin: caller.Address(),
blockNumber: block.BlockInfo().Number,
sender: tx.Sender(),
prevHash: block.PrevHash,
coinbase: block.Coinbase,
time: block.Time,
diff: block.Difficulty,
txValue: tx.Value,
txData: tx.Data,
// XXX Tx data? Could be just an argument to the closure instead
txData: nil,
})
closure.Call(vm, nil)
}

View File

@ -13,22 +13,31 @@ type Transaction struct {
Nonce uint64
Recipient []byte
Value *big.Int
Gas *big.Int
Gasprice *big.Int
Data []string
Memory []int
v byte
r, s []byte
}
func NewTransaction(to []byte, value *big.Int, data []string) *Transaction {
tx := Transaction{Recipient: to, Value: value}
tx.Nonce = 0
// Serialize the data
tx.Data = data
tx := Transaction{Recipient: to, Value: value, Nonce: 0, Data: data}
return &tx
}
func NewContractCreationTx(value, gasprice *big.Int, data []string) *Transaction {
return &Transaction{Value: value, Gasprice: gasprice, Data: data}
}
func NewContractMessageTx(to []byte, value, gasprice, gas *big.Int, data []string) *Transaction {
return &Transaction{Recipient: to, Value: value, Gasprice: gasprice, Gas: gas, Data: data}
}
func NewTx(to []byte, value *big.Int, data []string) *Transaction {
return &Transaction{Recipient: to, Value: value, Gasprice: big.NewInt(0), Gas: big.NewInt(0), Nonce: 0, Data: data}
}
// XXX Deprecated
func NewTransactionFromData(data []byte) *Transaction {
return NewTransactionFromBytes(data)

View File

@ -1,12 +1,12 @@
package ethchain
import (
"bytes"
"fmt"
_ "bytes"
_ "fmt"
"github.com/ethereum/eth-go/ethutil"
"github.com/obscuren/secp256k1-go"
_ "github.com/obscuren/secp256k1-go"
"log"
"math"
_ "math"
"math/big"
)
@ -18,122 +18,102 @@ type Vm struct {
mem map[string]*big.Int
vars RuntimeVars
state *State
}
type RuntimeVars struct {
address []byte
origin []byte
blockNumber uint64
sender []byte
prevHash []byte
coinbase []byte
time int64
diff *big.Int
txValue *big.Int
txData []string
}
func (vm *Vm) Process(contract *Contract, state *State, vars RuntimeVars) {
vm.mem = make(map[string]*big.Int)
vm.stack = NewStack()
func NewVm(state *State, vars RuntimeVars) *Vm {
return &Vm{vars: vars, state: state}
}
addr := vars.address // tx.Hash()[12:]
// Instruction pointer
pc := 0
var Pow256 = ethutil.BigPow(2, 256)
if contract == nil {
fmt.Println("Contract not found")
return
func (vm *Vm) RunClosure(closure *Closure) []byte {
// If the amount of gas supplied is less equal to 0
if closure.Gas.Cmp(big.NewInt(0)) <= 0 {
// TODO Do something
}
Pow256 := ethutil.BigPow(2, 256)
// Memory for the current closure
mem := &Memory{}
// New stack (should this be shared?)
stack := NewStack()
// Instruction pointer
pc := big.NewInt(0)
// Current step count
step := 0
// The base for all big integer arithmetic
base := new(big.Int)
if ethutil.Config.Debug {
ethutil.Config.Log.Debugf("# op\n")
}
stepcount := 0
totalFee := new(big.Int)
out:
for {
stepcount++
// The base big int for all calculations. Use this for any results.
base := new(big.Int)
val := contract.GetMem(pc)
//fmt.Printf("%x = %d, %v %x\n", r, len(r), v, nb)
step++
// Get the memory location of pc
val := closure.GetMem(pc)
// Get the opcode (it must be an opcode!)
op := OpCode(val.Uint())
var fee *big.Int = new(big.Int)
var fee2 *big.Int = new(big.Int)
if stepcount > 16 {
fee.Add(fee, StepFee)
}
// Calculate the fees
switch op {
case oSSTORE:
y, x := vm.stack.Peekn()
val := contract.Addr(ethutil.BigToBytes(x, 256))
if val.IsEmpty() && len(y.Bytes()) > 0 {
fee2.Add(DataFee, StoreFee)
} else {
fee2.Sub(DataFee, StoreFee)
}
case oSLOAD:
fee.Add(fee, StoreFee)
case oEXTRO, oBALANCE:
fee.Add(fee, ExtroFee)
case oSHA256, oRIPEMD160, oECMUL, oECADD, oECSIGN, oECRECOVER, oECVALID:
fee.Add(fee, CryptoFee)
case oMKTX:
fee.Add(fee, ContractFee)
}
tf := new(big.Int).Add(fee, fee2)
if contract.Amount.Cmp(tf) < 0 {
fmt.Println("Insufficient fees to continue running the contract", tf, contract.Amount)
break
}
// Add the fee to the total fee. It's subtracted when we're done looping
totalFee.Add(totalFee, tf)
if ethutil.Config.Debug {
ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
}
// TODO Get each instruction cost properly
fee := new(big.Int)
fee.Add(fee, big.NewInt(1000))
if closure.Gas.Cmp(fee) < 0 {
return closure.Return(nil)
}
switch op {
case oSTOP:
fmt.Println("")
break out
case oLOG:
stack.Print()
mem.Print()
case oSTOP: // Stop the closure
return closure.Return(nil)
// 0x20 range
case oADD:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// (x + y) % 2 ** 256
base.Add(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
vm.stack.Push(base)
stack.Push(base)
case oSUB:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// (x - y) % 2 ** 256
base.Sub(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
vm.stack.Push(base)
stack.Push(base)
case oMUL:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// (x * y) % 2 ** 256
base.Mul(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
vm.stack.Push(base)
stack.Push(base)
case oDIV:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// floor(x / y)
base.Div(x, y)
// Pop result back on the stack
vm.stack.Push(base)
stack.Push(base)
case oSDIV:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
@ -147,13 +127,13 @@ out:
z.Sub(Pow256, z)
}
// Push result on to the stack
vm.stack.Push(z)
stack.Push(z)
case oMOD:
x, y := vm.stack.Popn()
x, y := stack.Popn()
base.Mod(x, y)
vm.stack.Push(base)
stack.Push(base)
case oSMOD:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
@ -167,250 +147,189 @@ out:
z.Sub(Pow256, z)
}
// Push result on to the stack
vm.stack.Push(z)
stack.Push(z)
case oEXP:
x, y := vm.stack.Popn()
x, y := stack.Popn()
base.Exp(x, y, Pow256)
vm.stack.Push(base)
stack.Push(base)
case oNEG:
base.Sub(Pow256, vm.stack.Pop())
vm.stack.Push(base)
base.Sub(Pow256, stack.Pop())
stack.Push(base)
case oLT:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// x < y
if x.Cmp(y) < 0 {
vm.stack.Push(ethutil.BigTrue)
stack.Push(ethutil.BigTrue)
} else {
vm.stack.Push(ethutil.BigFalse)
}
case oLE:
x, y := vm.stack.Popn()
// x <= y
if x.Cmp(y) < 1 {
vm.stack.Push(ethutil.BigTrue)
} else {
vm.stack.Push(ethutil.BigFalse)
stack.Push(ethutil.BigFalse)
}
case oGT:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// x > y
if x.Cmp(y) > 0 {
vm.stack.Push(ethutil.BigTrue)
stack.Push(ethutil.BigTrue)
} else {
vm.stack.Push(ethutil.BigFalse)
}
case oGE:
x, y := vm.stack.Popn()
// x >= y
if x.Cmp(y) > -1 {
vm.stack.Push(ethutil.BigTrue)
} else {
vm.stack.Push(ethutil.BigFalse)
stack.Push(ethutil.BigFalse)
}
case oNOT:
x, y := vm.stack.Popn()
x, y := stack.Popn()
// x != y
if x.Cmp(y) != 0 {
vm.stack.Push(ethutil.BigTrue)
stack.Push(ethutil.BigTrue)
} else {
vm.stack.Push(ethutil.BigFalse)
}
case oMYADDRESS:
vm.stack.Push(ethutil.BigD(addr))
case oTXSENDER:
vm.stack.Push(ethutil.BigD(vars.sender))
case oTXVALUE:
vm.stack.Push(vars.txValue)
case oTXDATAN:
vm.stack.Push(big.NewInt(int64(len(vars.txData))))
case oTXDATA:
v := vm.stack.Pop()
// v >= len(data)
if v.Cmp(big.NewInt(int64(len(vars.txData)))) >= 0 {
vm.stack.Push(ethutil.Big("0"))
} else {
vm.stack.Push(ethutil.Big(vars.txData[v.Uint64()]))
}
case oBLK_PREVHASH:
vm.stack.Push(ethutil.BigD(vars.prevHash))
case oBLK_COINBASE:
vm.stack.Push(ethutil.BigD(vars.coinbase))
case oBLK_TIMESTAMP:
vm.stack.Push(big.NewInt(vars.time))
case oBLK_NUMBER:
vm.stack.Push(big.NewInt(int64(vars.blockNumber)))
case oBLK_DIFFICULTY:
vm.stack.Push(vars.diff)
case oBASEFEE:
// e = 10^21
e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0))
d := new(big.Rat)
d.SetInt(vars.diff)
c := new(big.Rat)
c.SetFloat64(0.5)
// d = diff / 0.5
d.Quo(d, c)
// base = floor(d)
base.Div(d.Num(), d.Denom())
x := new(big.Int)
x.Div(e, base)
// x = floor(10^21 / floor(diff^0.5))
vm.stack.Push(x)
case oSHA256, oSHA3, oRIPEMD160:
// This is probably save
// ceil(pop / 32)
length := int(math.Ceil(float64(vm.stack.Pop().Uint64()) / 32.0))
// New buffer which will contain the concatenated popped items
data := new(bytes.Buffer)
for i := 0; i < length; i++ {
// Encode the number to bytes and have it 32bytes long
num := ethutil.NumberToBytes(vm.stack.Pop().Bytes(), 256)
data.WriteString(string(num))
stack.Push(ethutil.BigFalse)
}
if op == oSHA256 {
vm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes())))
} else if op == oSHA3 {
vm.stack.Push(base.SetBytes(ethutil.Sha3Bin(data.Bytes())))
} else {
vm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes())))
}
case oECMUL:
y := vm.stack.Pop()
x := vm.stack.Pop()
//n := vm.stack.Pop()
// 0x10 range
case oAND:
case oOR:
case oXOR:
case oBYTE:
//if ethutil.Big(x).Cmp(ethutil.Big(y)) {
data := new(bytes.Buffer)
data.WriteString(x.String())
data.WriteString(y.String())
if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 {
// TODO
} else {
// Invalid, push infinity
vm.stack.Push(ethutil.Big("0"))
vm.stack.Push(ethutil.Big("0"))
}
//} else {
// // Invalid, push infinity
// vm.stack.Push("0")
// vm.stack.Push("0")
//}
// 0x20 range
case oSHA3:
case oECADD:
case oECSIGN:
case oECRECOVER:
case oECVALID:
case oPUSH:
pc++
vm.stack.Push(contract.GetMem(pc).BigInt())
case oPOP:
// Pop current value of the stack
vm.stack.Pop()
case oDUP:
// Dup top stack
x := vm.stack.Pop()
vm.stack.Push(x)
vm.stack.Push(x)
case oSWAP:
// Swap two top most values
x, y := vm.stack.Popn()
vm.stack.Push(y)
vm.stack.Push(x)
case oMLOAD:
x := vm.stack.Pop()
vm.stack.Push(vm.mem[x.String()])
case oMSTORE:
x, y := vm.stack.Popn()
vm.mem[x.String()] = y
case oSLOAD:
// Load the value in storage and push it on the stack
x := vm.stack.Pop()
// decode the object as a big integer
decoder := contract.Addr(x.Bytes())
if !decoder.IsNil() {
vm.stack.Push(decoder.BigInt())
} else {
vm.stack.Push(ethutil.BigFalse)
}
case oSSTORE:
// Store Y at index X
y, x := vm.stack.Popn()
addr := ethutil.BigToBytes(x, 256)
fmt.Printf(" => %x (%v) @ %v", y.Bytes(), y, ethutil.BigD(addr))
contract.SetAddr(addr, y)
//contract.State().Update(string(idx), string(y))
case oJMP:
x := int(vm.stack.Pop().Uint64())
// Set pc to x - 1 (minus one so the incrementing at the end won't effect it)
pc = x
pc--
case oJMPI:
x := vm.stack.Pop()
// Set pc to x if it's non zero
if x.Cmp(ethutil.BigFalse) != 0 {
pc = int(x.Uint64())
pc--
}
case oIND:
vm.stack.Push(big.NewInt(int64(pc)))
case oEXTRO:
memAddr := vm.stack.Pop()
contractAddr := vm.stack.Pop().Bytes()
// Push the contract's memory on to the stack
vm.stack.Push(contractMemory(state, contractAddr, memAddr))
// 0x30 range
case oADDRESS:
stack.Push(ethutil.BigD(closure.Object().Address()))
case oBALANCE:
// Pushes the balance of the popped value on to the stack
account := state.GetAccount(vm.stack.Pop().Bytes())
vm.stack.Push(account.Amount)
case oMKTX:
addr, value := vm.stack.Popn()
from, length := vm.stack.Popn()
stack.Push(closure.Value)
case oORIGIN:
stack.Push(ethutil.BigD(vm.vars.origin))
case oCALLER:
stack.Push(ethutil.BigD(closure.Callee().Address()))
case oCALLVALUE:
// FIXME: Original value of the call, not the current value
stack.Push(closure.Value)
case oCALLDATA:
offset := stack.Pop()
mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args)
case oCALLDATASIZE:
stack.Push(big.NewInt(int64(len(closure.Args))))
case oGASPRICE:
// TODO
makeInlineTx(addr.Bytes(), value, from, length, contract, state)
// 0x40 range
case oPREVHASH:
stack.Push(ethutil.BigD(vm.vars.prevHash))
case oCOINBASE:
stack.Push(ethutil.BigD(vm.vars.coinbase))
case oTIMESTAMP:
stack.Push(big.NewInt(vm.vars.time))
case oNUMBER:
stack.Push(big.NewInt(int64(vm.vars.blockNumber)))
case oDIFFICULTY:
stack.Push(vm.vars.diff)
case oGASLIMIT:
// TODO
// 0x50 range
case oPUSH: // Push PC+1 on to the stack
pc.Add(pc, ethutil.Big1)
val := closure.GetMem(pc).BigInt()
stack.Push(val)
case oPOP:
stack.Pop()
case oDUP:
stack.Push(stack.Peek())
case oSWAP:
x, y := stack.Popn()
stack.Push(y)
stack.Push(x)
case oMLOAD:
offset := stack.Pop()
stack.Push(ethutil.BigD(mem.Get(offset.Int64(), 32)))
case oMSTORE: // Store the value at stack top-1 in to memory at location stack top
// Pop value of the stack
val, mStart := stack.Popn()
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
case oMSTORE8:
val, mStart := stack.Popn()
base.And(val, new(big.Int).SetInt64(0xff))
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256))
case oSLOAD:
loc := stack.Pop()
val := closure.GetMem(loc)
stack.Push(val.BigInt())
case oSSTORE:
val, loc := stack.Popn()
closure.SetMem(loc, ethutil.NewValue(val))
case oJUMP:
pc = stack.Pop()
case oJUMPI:
pos, cond := stack.Popn()
if cond.Cmp(big.NewInt(0)) > 0 {
pc = pos
}
case oPC:
stack.Push(pc)
case oMSIZE:
stack.Push(big.NewInt(int64(mem.Len())))
// 0x60 range
case oCALL:
// Pop return size and offset
retSize, retOffset := stack.Popn()
// Pop input size and offset
inSize, inOffset := stack.Popn()
// Get the arguments from the memory
args := mem.Get(inOffset.Int64(), inSize.Int64())
// Pop gas and value of the stack.
gas, value := stack.Popn()
// Closure addr
addr := stack.Pop()
// Fetch the contract which will serve as the closure body
contract := vm.state.GetContract(addr.Bytes())
// Create a new callable closure
closure := NewClosure(closure, contract, vm.state, gas, value)
// Executer the closure and get the return value (if any)
ret := closure.Call(vm, args)
mem.Set(retOffset.Int64(), retSize.Int64(), ret)
case oRETURN:
size, offset := stack.Popn()
ret := mem.Get(offset.Int64(), size.Int64())
return closure.Return(ret)
case oSUICIDE:
recAddr := vm.stack.Pop().Bytes()
// Purge all memory
deletedMemory := contract.state.Purge()
// Add refunds to the pop'ed address
refund := new(big.Int).Mul(StoreFee, big.NewInt(int64(deletedMemory)))
account := state.GetAccount(recAddr)
account.Amount.Add(account.Amount, refund)
// Update the refunding address
state.UpdateAccount(recAddr, account)
// Delete the contract
state.trie.Update(string(addr), "")
/*
recAddr := stack.Pop().Bytes()
// Purge all memory
deletedMemory := contract.state.Purge()
// Add refunds to the pop'ed address
refund := new(big.Int).Mul(StoreFee, big.NewInt(int64(deletedMemory)))
account := state.GetAccount(recAddr)
account.Amount.Add(account.Amount, refund)
// Update the refunding address
state.UpdateAccount(recAddr, account)
// Delete the contract
state.trie.Update(string(addr), "")
ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr)
break out
ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr)
break out
*/
default:
fmt.Printf("Invalid OPCODE: %x\n", op)
ethutil.Config.Log.Debugln("Invalid opcode", op)
}
ethutil.Config.Log.Debugln("")
//vm.stack.Print()
pc++
}
state.UpdateContract(addr, contract)
pc.Add(pc, ethutil.Big1)
}
}
func makeInlineTx(addr []byte, value, from, length *big.Int, contract *Contract, state *State) {
ethutil.Config.Log.Debugf(" => creating inline tx %x %v %v %v", addr, value, from, length)
j := 0
j := int64(0)
dataItems := make([]string, int(length.Uint64()))
for i := from.Uint64(); i < length.Uint64(); i++ {
dataItems[j] = contract.GetMem(j).Str()
for i := from.Int64(); i < length.Int64(); i++ {
dataItems[j] = contract.GetMem(big.NewInt(j)).Str()
j++
}
tx := NewTransaction(addr, value, dataItems)
if tx.IsContract() {
contract := MakeContract(tx, state)
state.UpdateContract(tx.Hash()[12:], contract)
state.UpdateContract(contract)
} else {
account := state.GetAccount(tx.Recipient)
account.Amount.Add(account.Amount, tx.Value)

View File

@ -1,13 +1,15 @@
package ethchain
import (
"fmt"
"bytes"
"github.com/ethereum/eth-go/ethdb"
"github.com/ethereum/eth-go/ethutil"
"math/big"
"testing"
)
/*
func TestRun(t *testing.T) {
InitFees()
@ -104,3 +106,69 @@ func TestRun2(t *testing.T) {
txData: tx.Data,
})
}
*/
// XXX Full stack test
func TestRun3(t *testing.T) {
ethutil.ReadConfig("")
db, _ := ethdb.NewMemDatabase()
state := NewState(ethutil.NewTrie(db, ""))
script := Compile([]string{
"PUSH", "300",
"PUSH", "0",
"MSTORE",
"PUSH", "32",
"CALLDATA",
"PUSH", "64",
"PUSH", "0",
"RETURN",
})
tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script)
addr := tx.Hash()[12:]
contract := MakeContract(tx, state)
state.UpdateContract(contract)
callerScript := ethutil.Compile(
"PUSH", 1337, // Argument
"PUSH", 65, // argument mem offset
"MSTORE",
"PUSH", 64, // ret size
"PUSH", 0, // ret offset
"PUSH", 32, // arg size
"PUSH", 65, // arg offset
"PUSH", 1000, /// Gas
"PUSH", 0, /// value
"PUSH", addr, // Sender
"CALL",
"PUSH", 64,
"PUSH", 0,
"RETURN",
)
callerTx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), callerScript)
// Contract addr as test address
account := NewAccount(ContractAddr, big.NewInt(10000000))
callerClosure := NewClosure(account, MakeContract(callerTx, state), state, big.NewInt(1000000000), new(big.Int))
vm := NewVm(state, RuntimeVars{
origin: account.Address(),
blockNumber: 1,
prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
time: 1,
diff: big.NewInt(256),
// XXX Tx data? Could be just an argument to the closure instead
txData: nil,
})
ret := callerClosure.Call(vm, nil)
exp := []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, 1, 44, 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, 5, 57}
if bytes.Compare(ret, exp) != 0 {
t.Errorf("expected return value to be %v, got %v", exp, ret)
}
}

View File

@ -33,3 +33,9 @@ func CurrencyToString(num *big.Int) string {
return fmt.Sprintf("%v Wei", num)
}
var (
Big1 = big.NewInt(1)
Big0 = big.NewInt(0)
Big256 = big.NewInt(0xff)
)

View File

@ -6,6 +6,7 @@ import (
"os"
"os/user"
"path"
"runtime"
)
type LogType byte
@ -19,12 +20,13 @@ const (
type config struct {
Db Database
Log *Logger
ExecPath string
Debug bool
Ver string
Pubkey []byte
Seed bool
Log *Logger
ExecPath string
Debug bool
Ver string
ClientString string
Pubkey []byte
Seed bool
}
var Config *config
@ -48,11 +50,16 @@ func ReadConfig(base string) *config {
Config = &config{ExecPath: path, Debug: true, Ver: "0.3.1"}
Config.Log = NewLogger(LogFile|LogStd, LogLevelDebug)
Config.SetClientString("/Ethereum(G)")
}
return Config
}
func (c *config) SetClientString(str string) {
Config.ClientString = fmt.Sprintf("%s nv%s/%s", str, c.Ver, runtime.GOOS)
}
type LoggerType byte
const (

View File

@ -7,57 +7,72 @@ import (
// Op codes
var OpCodes = map[string]byte{
"STOP": 0x00,
"ADD": 0x01,
"MUL": 0x02,
"SUB": 0x03,
"DIV": 0x04,
"SDIV": 0x05,
"MOD": 0x06,
"SMOD": 0x07,
"EXP": 0x08,
"NEG": 0x09,
"LT": 0x0a,
"LE": 0x0b,
"GT": 0x0c,
"GE": 0x0d,
"EQ": 0x0e,
"NOT": 0x0f,
"MYADDRESS": 0x10,
"TXSENDER": 0x11,
"TXVALUE": 0x12,
"TXDATAN": 0x13,
"TXDATA": 0x14,
"BLK_PREVHASH": 0x15,
"BLK_COINBASE": 0x16,
"BLK_TIMESTAMP": 0x17,
"BLK_NUMBER": 0x18,
"BLK_DIFFICULTY": 0x19,
"BLK_NONCE": 0x1a,
"BASEFEE": 0x1b,
"SHA256": 0x20,
"RIPEMD160": 0x21,
"ECMUL": 0x22,
"ECADD": 0x23,
"ECSIGN": 0x24,
"ECRECOVER": 0x25,
"ECVALID": 0x26,
"SHA3": 0x27,
"PUSH": 0x30,
"POP": 0x31,
"DUP": 0x32,
"SWAP": 0x33,
"MLOAD": 0x34,
"MSTORE": 0x35,
"SLOAD": 0x36,
"SSTORE": 0x37,
"JMP": 0x38,
"JMPI": 0x39,
"IND": 0x3a,
"EXTRO": 0x3b,
"BALANCE": 0x3c,
"MKTX": 0x3d,
"SUICIDE": 0x3f,
// 0x0 range - arithmetic ops
"STOP": 0x00,
"ADD": 0x01,
"MUL": 0x02,
"SUB": 0x03,
"DIV": 0x04,
"SDIV": 0x05,
"MOD": 0x06,
"SMOD": 0x07,
"EXP": 0x08,
"NEG": 0x09,
"LT": 0x0a,
"GT": 0x0b,
"EQ": 0x0c,
"NOT": 0x0d,
// 0x10 range - bit ops
"AND": 0x10,
"OR": 0x11,
"XOR": 0x12,
"BYTE": 0x13,
// 0x20 range - crypto
"SHA3": 0x20,
// 0x30 range - closure state
"ADDRESS": 0x30,
"BALANCE": 0x31,
"ORIGIN": 0x32,
"CALLER": 0x33,
"CALLVALUE": 0x34,
"CALLDATA": 0x35,
"CALLDATASIZE": 0x36,
"GASPRICE": 0x38,
// 0x40 range - block operations
"PREVHASH": 0x40,
"COINBASE": 0x41,
"TIMESTAMP": 0x42,
"NUMBER": 0x43,
"DIFFICULTY": 0x44,
"GASLIMIT": 0x45,
// 0x50 range - 'storage' and execution
"PUSH": 0x50,
"POP": 0x51,
"DUP": 0x52,
"SWAP": 0x53,
"MLOAD": 0x54,
"MSTORE": 0x55,
"MSTORE8": 0x56,
"SLOAD": 0x57,
"SSTORE": 0x58,
"JUMP": 0x59,
"JUMPI": 0x5a,
"PC": 0x5b,
"MSIZE": 0x5c,
// 0x60 range - closures
"CREATE": 0x60,
"CALL": 0x61,
"RETURN": 0x62,
// 0x70 range - other
"LOG": 0x70,
"SUICIDE": 0x7f,
}
func IsOpCode(s string) bool {
@ -69,16 +84,30 @@ func IsOpCode(s string) bool {
return false
}
func CompileInstr(s string) ([]byte, error) {
isOp := IsOpCode(s)
if isOp {
return []byte{OpCodes[s]}, nil
func CompileInstr(s interface{}) ([]byte, error) {
switch s.(type) {
case string:
str := s.(string)
isOp := IsOpCode(str)
if isOp {
return []byte{OpCodes[str]}, nil
}
num := new(big.Int)
_, success := num.SetString(str, 0)
// Assume regular bytes during compilation
if !success {
num.SetBytes([]byte(str))
}
return num.Bytes(), nil
case int:
return big.NewInt(int64(s.(int))).Bytes(), nil
case []byte:
return BigD(s.([]byte)).Bytes(), nil
}
num := new(big.Int)
num.SetString(s, 0)
return num.Bytes(), nil
return nil, nil
}
func Instr(instr string) (int, []string, error) {
@ -99,3 +128,17 @@ func Instr(instr string) (int, []string, error) {
return op, args[1:7], nil
}
// Script compilation functions
// Compiles strings to machine code
func Compile(instructions ...interface{}) (script []string) {
script = make([]string, len(instructions))
for i, val := range instructions {
instr, _ := CompileInstr(val)
script[i] = string(instr)
}
return
}

View File

@ -9,6 +9,10 @@ import (
"math/big"
)
type RlpEncodable interface {
RlpEncode() []byte
}
type RlpEncoder struct {
rlpData []byte
}

View File

@ -1,6 +1,7 @@
package ethutil
import (
"fmt"
"reflect"
"testing"
)

View File

@ -7,7 +7,6 @@ import (
"github.com/ethereum/eth-go/ethutil"
"github.com/ethereum/eth-go/ethwire"
"net"
"runtime"
"strconv"
"strings"
"sync/atomic"
@ -160,7 +159,7 @@ func NewOutboundPeer(addr string, ethereum *Ethereum, caps Caps) *Peer {
connected: 0,
disconnect: 0,
caps: caps,
Version: fmt.Sprintf("/Ethereum(G) v%s/%s", ethutil.Config.Ver, runtime.GOOS),
Version: ethutil.Config.ClientString,
}
// Set up the connection in another goroutine so we don't block the main thread