Skip to content
Snippets Groups Projects
transactor.go 8.49 KiB
Newer Older
Androlo's avatar
Androlo committed
package pipe

import (
androlo's avatar
androlo committed
	"bytes"
Androlo's avatar
Androlo committed
	"encoding/hex"
	"fmt"
androlo's avatar
androlo committed
	"time"
Ethan Buchman's avatar
Ethan Buchman committed

	"github.com/eris-ltd/eris-db/account"
	"github.com/eris-ltd/eris-db/evm"
	"github.com/eris-ltd/eris-db/state"
	"github.com/eris-ltd/eris-db/txs"

Ethan Buchman's avatar
Ethan Buchman committed
	cmn "github.com/tendermint/go-common"
	"github.com/tendermint/go-crypto"
	tEvents "github.com/tendermint/go-events"
Ethan Buchman's avatar
Ethan Buchman committed

	"github.com/eris-ltd/eris-db/tmsp"
Androlo's avatar
Androlo committed
)

type transactor struct {
Ethan Buchman's avatar
Ethan Buchman committed
	eventSwitch  tEvents.Fireable
	erisdbApp    *tmsp.ErisDBApp
	eventEmitter EventEmitter
	txMtx        *sync.Mutex
Androlo's avatar
Androlo committed
}

Ethan Buchman's avatar
Ethan Buchman committed
func newTransactor(eventSwitch tEvents.Fireable, erisdbApp *tmsp.ErisDBApp, eventEmitter EventEmitter) *transactor {
Androlo's avatar
Androlo committed
	txs := &transactor{
Ethan Buchman's avatar
Ethan Buchman committed
		erisdbApp,
Androlo's avatar
Androlo committed
		eventEmitter,
Androlo's avatar
Androlo committed
	}
	return txs
Androlo's avatar
Androlo committed
}

// Run a contract's code on an isolated and unpersisted state
// Cannot be used to create new contracts
androlo's avatar
androlo committed
func (this *transactor) Call(fromAddress, toAddress, data []byte) (*Call, error) {
Androlo's avatar
Androlo committed

	cache := this.erisdbApp.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx)
androlo's avatar
androlo committed
	outAcc := cache.GetAccount(toAddress)
Androlo's avatar
Androlo committed
	if outAcc == nil {
androlo's avatar
androlo committed
		return nil, fmt.Errorf("Account %X does not exist", toAddress)
	}
	if fromAddress == nil {
		fromAddress = []byte{}
Androlo's avatar
Androlo committed
	}
	callee := toVMAccount(outAcc)
androlo's avatar
androlo committed
	caller := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)}
Androlo's avatar
Androlo committed
	txCache := state.NewTxCache(cache)
	st := this.erisdbApp.GetState() // for block height, time
Androlo's avatar
Androlo committed
	params := vm.Params{
		BlockHeight: int64(st.LastBlockHeight),
Androlo's avatar
Androlo committed
		BlockHash:   cmn.LeftPadWord256(st.LastBlockHash),
		BlockTime:   st.LastBlockTime.Unix(),
		GasLimit:    10000000,
	}

	vmach := vm.NewVM(txCache, params, caller.Address, nil)
	vmach.SetFireable(this.eventSwitch)
androlo's avatar
androlo committed
	gas := int64(1000000000)
Androlo's avatar
Androlo committed
	ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas)
	if err != nil {
		return nil, err
	}
	return &Call{Return: hex.EncodeToString(ret)}, nil
}

// Run the given code on an isolated and unpersisted state
// Cannot be used to create new contracts.
androlo's avatar
androlo committed
func (this *transactor) CallCode(fromAddress, code, data []byte) (*Call, error) {
	if fromAddress == nil {
		fromAddress = []byte{}
	}
	cache := this.erisdbApp.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx)
androlo's avatar
androlo committed
	callee := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)}
	caller := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)}
Androlo's avatar
Androlo committed
	txCache := state.NewTxCache(cache)
	st := this.erisdbApp.GetState() // for block height, time
Androlo's avatar
Androlo committed
	params := vm.Params{
		BlockHeight: int64(st.LastBlockHeight),
Androlo's avatar
Androlo committed
		BlockHash:   cmn.LeftPadWord256(st.LastBlockHash),
		BlockTime:   st.LastBlockTime.Unix(),
		GasLimit:    10000000,
	}

	vmach := vm.NewVM(txCache, params, caller.Address, nil)
	gas := int64(1000000000)
Androlo's avatar
Androlo committed
	ret, err := vmach.Call(caller, callee, code, data, 0, &gas)
	if err != nil {
		return nil, err
	}
	return &Call{Return: hex.EncodeToString(ret)}, nil
}

// Broadcast a transaction.
func (this *transactor) BroadcastTx(tx txs.Tx) (*Receipt, error) {

	err := this.erisdbApp.BroadcastTx(tx)
Androlo's avatar
Androlo committed
	if err != nil {
		return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
	}
Ethan Buchman's avatar
Ethan Buchman committed

	chainId := config.GetString("erisdb.chain_id")
	txHash := txs.TxID(chainId, tx)
Androlo's avatar
Androlo committed
	var createsContract uint8
	var contractAddr []byte
	// check if creates new contract
	if callTx, ok := tx.(*txs.CallTx); ok {
Androlo's avatar
Androlo committed
		if len(callTx.Address) == 0 {
			createsContract = 1
			contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence)
Androlo's avatar
Androlo committed
		}
	}
	return &Receipt{txHash, createsContract, contractAddr}, nil
}

// Get all unconfirmed txs.
Androlo's avatar
Androlo committed
func (this *transactor) UnconfirmedTxs() (*UnconfirmedTxs, error) {
Ethan Buchman's avatar
Ethan Buchman committed
	// TODO-RPC
	return &UnconfirmedTxs{}, nil
Androlo's avatar
Androlo committed
}

// Orders calls to BroadcastTx using lock (waits for response from core before releasing)
func (this *transactor) Transact(privKey, address, data []byte, gasLimit, fee int64) (*Receipt, error) {
Androlo's avatar
Androlo committed
	var addr []byte
	if len(address) == 0 {
		addr = nil
	} else if len(address) != 20 {
Androlo's avatar
Androlo committed
		return nil, fmt.Errorf("Address is not of the right length: %d\n", len(address))
Androlo's avatar
Androlo committed
	} else {
Androlo's avatar
Androlo committed
		addr = address
Androlo's avatar
Androlo committed
	}
	if len(privKey) != 64 {
Androlo's avatar
Androlo committed
		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
Androlo's avatar
Androlo committed
	}
	this.txMtx.Lock()
	defer this.txMtx.Unlock()
Ethan Buchman's avatar
Ethan Buchman committed
	pa := account.GenPrivAccountFromPrivKeyBytes(privKey)
	cache := this.erisdbApp.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx)
Androlo's avatar
Androlo committed
	acc := cache.GetAccount(pa.Address)
	var sequence int
Androlo's avatar
Androlo committed
	if acc == nil {
		sequence = 1
	} else {
		sequence = acc.Sequence + 1
	}
Androlo's avatar
Androlo committed
	// fmt.Printf("Sequence %d\n", sequence)
	txInput := &txs.TxInput{
Androlo's avatar
Androlo committed
		Address:  pa.Address,
androlo's avatar
androlo committed
		Amount:   1,
Androlo's avatar
Androlo committed
		Sequence: sequence,
		PubKey:   pa.PubKey,
	}
	tx := &txs.CallTx{
Androlo's avatar
Androlo committed
		Input:    txInput,
		Address:  addr,
		GasLimit: gasLimit,
		Fee:      fee,
Androlo's avatar
Androlo committed
		Data:     data,
	}
Androlo's avatar
Androlo committed
	// Got ourselves a tx.
	txS, errS := this.SignTx(tx, []*account.PrivAccount{pa})
	if errS != nil {
		return nil, errS
	}
	return this.BroadcastTx(txS)
}

func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) {
androlo's avatar
androlo committed
	rec, tErr := this.Transact(privKey, address, data, gasLimit, fee)
	if tErr != nil {
		return nil, tErr
	}
	var addr []byte
	if rec.CreatesContract == 1 {
		addr = rec.ContractAddr
	} else {
		addr = address
	}
	wc := make(chan *txs.EventDataCall)
androlo's avatar
androlo committed
	subId := fmt.Sprintf("%X", rec.TxHash)
	this.eventEmitter.Subscribe(subId, txs.EventStringAccCall(addr), func(evt tEvents.EventData) {
		event := evt.(txs.EventDataCall)
androlo's avatar
androlo committed
		if bytes.Equal(event.TxID, rec.TxHash) {
			wc <- &event
		}
	})

	timer := time.NewTimer(300 * time.Second)
androlo's avatar
androlo committed
	toChan := timer.C
	var ret *txs.EventDataCall
androlo's avatar
androlo committed
	var rErr error
androlo's avatar
androlo committed
	select {
	case <-toChan:
		rErr = fmt.Errorf("Transaction timed out. Hash: " + subId)
	case e := <-wc:
		timer.Stop()
		if e.Exception != "" {
			rErr = fmt.Errorf("Error when transacting: " + e.Exception)
		} else {
			ret = e
		}
	}
	this.eventEmitter.Unsubscribe(subId)
	return ret, rErr
}

androlo's avatar
androlo committed
func (this *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*Receipt, error) {
androlo's avatar
androlo committed

androlo's avatar
androlo committed
	if len(privKey) != 64 {
		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
	}
Androlo's avatar
Androlo committed
	this.txMtx.Lock()
	defer this.txMtx.Unlock()
Ethan Buchman's avatar
Ethan Buchman committed
	pa := account.GenPrivAccountFromPrivKeyBytes(privKey)
	cache := this.erisdbApp.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx)
androlo's avatar
androlo committed
	acc := cache.GetAccount(pa.Address)
	var sequence int
	if acc == nil {
		sequence = 1
	} else {
		sequence = acc.Sequence + 1
	}
	tx := txs.NewNameTxWithNonce(pa.PubKey, name, data, amount, fee, sequence)
androlo's avatar
androlo committed
	// Got ourselves a tx.
	txS, errS := this.SignTx(tx, []*account.PrivAccount{pa})
	if errS != nil {
		return nil, errS
	}
	return this.BroadcastTx(txS)
}

Androlo's avatar
Androlo committed
// Sign a transaction
func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) {
Androlo's avatar
Androlo committed
	// more checks?

	for i, privAccount := range privAccounts {
		if privAccount == nil || privAccount.PrivKey == nil {
			return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i)
		}
	}
	chainId := config.GetString("erisdb.chain_id")
Androlo's avatar
Androlo committed
	switch tx.(type) {
	case *txs.NameTx:
		nameTx := tx.(*txs.NameTx)
androlo's avatar
androlo committed
		nameTx.Input.PubKey = privAccounts[0].PubKey
		nameTx.Input.Signature = privAccounts[0].Sign(config.GetString("erisdb.chain_id"), nameTx)
	case *txs.SendTx:
		sendTx := tx.(*txs.SendTx)
Androlo's avatar
Androlo committed
		for i, input := range sendTx.Inputs {
			input.PubKey = privAccounts[i].PubKey
			input.Signature = privAccounts[i].Sign(chainId, sendTx)
		}
		break
	case *txs.CallTx:
		callTx := tx.(*txs.CallTx)
Androlo's avatar
Androlo committed
		callTx.Input.PubKey = privAccounts[0].PubKey
		callTx.Input.Signature = privAccounts[0].Sign(chainId, callTx)
		break
	case *txs.BondTx:
		bondTx := tx.(*txs.BondTx)
Androlo's avatar
Androlo committed
		// the first privaccount corresponds to the BondTx pub key.
		// the rest to the inputs
Ethan Buchman's avatar
Ethan Buchman committed
		bondTx.Signature = privAccounts[0].Sign(chainId, bondTx).(crypto.SignatureEd25519)
Androlo's avatar
Androlo committed
		for i, input := range bondTx.Inputs {
			input.PubKey = privAccounts[i+1].PubKey
			input.Signature = privAccounts[i+1].Sign(chainId, bondTx)
		}
		break
	case *txs.UnbondTx:
		unbondTx := tx.(*txs.UnbondTx)
Ethan Buchman's avatar
Ethan Buchman committed
		unbondTx.Signature = privAccounts[0].Sign(chainId, unbondTx).(crypto.SignatureEd25519)
Androlo's avatar
Androlo committed
		break
	case *txs.RebondTx:
		rebondTx := tx.(*txs.RebondTx)
Ethan Buchman's avatar
Ethan Buchman committed
		rebondTx.Signature = privAccounts[0].Sign(chainId, rebondTx).(crypto.SignatureEd25519)
Androlo's avatar
Androlo committed
		break
	default:
		return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx)
	}
	return tx, nil
}

// No idea what this does.
func toVMAccount(acc *account.Account) *vm.Account {
	return &vm.Account{
androlo's avatar
androlo committed
		Address: cmn.LeftPadWord256(acc.Address),
		Balance: acc.Balance,
		Code:    acc.Code,
		Nonce:   int64(acc.Sequence),
		Other:   acc.PubKey,
Androlo's avatar
Androlo committed
	}
}