diff --git a/app/app.go b/app/app.go index 82ebfb47b..2c4e434e7 100644 --- a/app/app.go +++ b/app/app.go @@ -1,11 +1,8 @@ package app import ( - "fmt" - "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" - "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" gov "github.com/tendermint/governmint/gov" eyes "github.com/tendermint/merkleeyes/client" @@ -36,14 +33,13 @@ func (app *Basecoin) Info() string { func (app *Basecoin) SetOption(key string, value string) (log string) { if key == "setAccount" { var err error - var setAccount types.PubAccount + var setAccount types.Account wire.ReadJSONPtr(&setAccount, []byte(value), &err) if err != nil { return "Error decoding setAccount message: " + err.Error() } - pubKeyBytes := wire.BinaryBytes(setAccount.PubKey) - accBytes := wire.BinaryBytes(setAccount.Account) - err = app.eyesCli.SetSync(pubKeyBytes, accBytes) + accBytes := wire.BinaryBytes(setAccount) + err = app.eyesCli.SetSync(setAccount.PubKey.Address(), accBytes) if err != nil { return "Error saving account: " + err.Error() } @@ -53,67 +49,61 @@ func (app *Basecoin) SetOption(key string, value string) (log string) { } // TMSP::AppendTx -func (app *Basecoin) AppendTx(txBytes []byte) (code tmsp.CodeType, result []byte, log string) { +func (app *Basecoin) AppendTx(txBytes []byte) (res tmsp.Result) { if len(txBytes) > maxTxSize { - return tmsp.CodeType_BaseEncodingError, nil, "Tx size exceeds maximum" + return types.ErrEncodingError.AppendLog("Tx size exceeds maximum") } // Decode tx var tx types.Tx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { - return tmsp.CodeType_BaseEncodingError, nil, "Error decoding tx: " + err.Error() + return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate tx - code, errStr := validateTx(tx) - if errStr != "" { - return code, nil, "Error validating tx: " + errStr + res = validateTx(tx) + if !res.IsOK() { + return res.PrependLog("Error validating tx") } - // Load accounts - accMap := loadAccounts(app.eyesCli, allPubKeys(tx)) // Execute tx - accs, code, errStr := runTx(tx, accMap, false) - if errStr != "" { - return code, nil, "Error executing tx: " + errStr - } - // Store accounts - storeAccounts(app.eyesCli, accs) - return tmsp.CodeType_OK, nil, "Success" + // TODO: get or make state with app.eeysCli, pass it to + // state.execution.go ExecTx + // Synchronize the txCache. + //storeAccounts(app.eyesCli, accs) + return types.ResultOK } // TMSP::CheckTx -func (app *Basecoin) CheckTx(txBytes []byte) (code tmsp.CodeType, result []byte, log string) { +func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) { if len(txBytes) > maxTxSize { - return tmsp.CodeType_BaseEncodingError, nil, "Tx size exceeds maximum" + return types.ErrEncodingError.AppendLog("Tx size exceeds maximum") } // Decode tx var tx types.Tx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { - return tmsp.CodeType_BaseEncodingError, nil, "Error decoding tx: " + err.Error() + return types.ErrEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate tx - code, errStr := validateTx(tx) - if errStr != "" { - return code, nil, "Error validating tx: " + errStr + res = validateTx(tx) + if !res.IsOK() { + return res.PrependLog("Error validating tx") } - // Load accounts - accMap := loadAccounts(app.eyesCli, allPubKeys(tx)) // Execute tx - _, code, errStr = runTx(tx, accMap, false) - if errStr != "" { - return code, nil, "Error (mock) executing tx: " + errStr - } - return tmsp.CodeType_OK, nil, "Success" + // TODO: get or make state with app.eeysCli, pass it to + // state.execution.go ExecTx + // Synchronize the txCache. + //storeAccounts(app.eyesCli, accs) + return types.ResultOK.SetLog("Success") } // TMSP::Query -func (app *Basecoin) Query(query []byte) (code tmsp.CodeType, result []byte, log string) { - return tmsp.CodeType_OK, nil, "" +func (app *Basecoin) Query(query []byte) (res tmsp.Result) { + return types.ResultOK value, err := app.eyesCli.GetSync(query) if err != nil { panic("Error making query: " + err.Error()) } - return tmsp.CodeType_OK, value, "Success" + return types.ResultOK.SetData(value).SetLog("Success") } // TMSP::Commit @@ -134,218 +124,3 @@ func (app *Basecoin) InitChain(validators []*tmsp.Validator) { func (app *Basecoin) EndBlock(height uint64) []*tmsp.Validator { return app.govMint.EndBlock(height) } - -//---------------------------------------- - -func validateTx(tx types.Tx) (code tmsp.CodeType, errStr string) { - inputs, outputs := tx.GetInputs(), tx.GetOutputs() - if len(inputs) == 0 { - return tmsp.CodeType_BaseEncodingError, "Tx.Inputs length cannot be 0" - } - seenPubKeys := map[string]bool{} - signBytes := tx.SignBytes() - for _, input := range inputs { - code, errStr = validateInput(input, signBytes) - if errStr != "" { - return - } - keyString := input.PubKey.KeyString() - if seenPubKeys[keyString] { - return tmsp.CodeType_BaseEncodingError, "Duplicate input pubKey" - } - seenPubKeys[keyString] = true - } - for _, output := range outputs { - code, errStr = validateOutput(output) - if errStr != "" { - return - } - keyString := output.PubKey.KeyString() - if seenPubKeys[keyString] { - return tmsp.CodeType_BaseEncodingError, "Duplicate output pubKey" - } - seenPubKeys[keyString] = true - } - sumInputs, overflow := sumAmounts(inputs, nil, 0) - if overflow { - return tmsp.CodeType_BaseEncodingError, "Input amount overflow" - } - sumOutputsPlus, overflow := sumAmounts(nil, outputs, len(inputs)+len(outputs)) - if overflow { - return tmsp.CodeType_BaseEncodingError, "Output amount overflow" - } - if sumInputs < sumOutputsPlus { - return tmsp.CodeType_BaseInsufficientFees, "Insufficient fees" - } - return tmsp.CodeType_OK, "" -} - -func validateInput(input types.Input, signBytes []byte) (code tmsp.CodeType, errStr string) { - if input.Amount == 0 { - return tmsp.CodeType_BaseEncodingError, "Input amount cannot be zero" - } - if input.PubKey == nil { - return tmsp.CodeType_BaseEncodingError, "Input pubKey cannot be nil" - } - if !input.PubKey.VerifyBytes(signBytes, input.Signature) { - return tmsp.CodeType_BaseUnauthorized, "Invalid signature" - } - return tmsp.CodeType_OK, "" -} - -func validateOutput(output types.Output) (code tmsp.CodeType, errStr string) { - if output.Amount == 0 { - return tmsp.CodeType_BaseEncodingError, "Output amount cannot be zero" - } - if output.PubKey == nil { - return tmsp.CodeType_BaseEncodingError, "Output pubKey cannot be nil" - } - return tmsp.CodeType_OK, "" -} - -func sumAmounts(inputs []types.Input, outputs []types.Output, more int) (total uint64, overflow bool) { - total = uint64(more) - for _, input := range inputs { - total2 := total + input.Amount - if total2 < total { - return 0, true - } - total = total2 - } - for _, output := range outputs { - total2 := total + output.Amount - if total2 < total { - return 0, true - } - total = total2 - } - return total, false -} - -// Returns accounts in order of types.Tx inputs and outputs -// appendTx: true if this is for AppendTx. -// TODO: create more intelligent sequence-checking. Current impl is just for a throughput demo. -func runTx(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) { - switch tx := tx.(type) { - case *types.SendTx: - return runSendTx(tx, accMap, appendTx) - case *types.GovTx: - return runGovTx(tx, accMap, appendTx) - } - return nil, tmsp.CodeType_InternalError, "Unknown transaction type" -} - -func processInputsOutputs(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) { - inputs, outputs := tx.GetInputs(), tx.GetOutputs() - accs = make([]types.PubAccount, 0, len(inputs)+len(outputs)) - // Deduct from inputs - // TODO refactor, duplicated code. - for _, input := range inputs { - var acc, ok = accMap[input.PubKey.KeyString()] - if !ok { - return nil, tmsp.CodeType_BaseUnknownAccount, "Input account does not exist" - } - if appendTx { - if acc.Sequence != input.Sequence { - return nil, tmsp.CodeType_BaseBadNonce, "Invalid sequence" - } - } else { - if acc.Sequence > input.Sequence { - return nil, tmsp.CodeType_BaseBadNonce, "Invalid sequence (too low)" - } - } - if acc.Balance < input.Amount { - return nil, tmsp.CodeType_BaseInsufficientFunds, "Insufficient funds" - } - // Good! - acc.Sequence++ - acc.Balance -= input.Amount - accs = append(accs, acc) - } - // Add to outputs - for _, output := range outputs { - var acc, ok = accMap[output.PubKey.KeyString()] - if !ok { - // Create new account if it doesn't already exist. - acc = types.PubAccount{ - PubKey: output.PubKey, - Account: types.Account{ - Balance: output.Amount, - }, - } - accMap[output.PubKey.KeyString()] = acc - accs = append(accs, acc) - } else { - // Good! - if (acc.Balance + output.Amount) < acc.Balance { - return nil, tmsp.CodeType_InternalError, "Output balance overflow in runTx" - } - acc.Balance += output.Amount - accs = append(accs, acc) - } - } - return accs, tmsp.CodeType_OK, "" -} - -func runSendTx(tx types.Tx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) { - return processInputsOutputs(tx, accMap, appendTx) -} - -func runGovTx(tx *types.GovTx, accMap map[string]types.PubAccount, appendTx bool) (accs []types.PubAccount, code tmsp.CodeType, errStr string) { - accs, code, errStr = processInputsOutputs(tx, accMap, appendTx) - // XXX run GovTx - return -} - -//---------------------------------------- - -func loadAccounts(eyesCli *eyes.Client, pubKeys []crypto.PubKey) (accMap map[string]types.PubAccount) { - accMap = make(map[string]types.PubAccount, len(pubKeys)) - for _, pubKey := range pubKeys { - keyString := pubKey.KeyString() - accBytes, err := eyesCli.GetSync([]byte(keyString)) - if err != nil { - panic("Error loading account: " + err.Error()) - } - if len(accBytes) == 0 { - continue - } - var acc types.Account - err = wire.ReadBinaryBytes(accBytes, &acc) - if err != nil { - panic("Error reading account: " + err.Error()) - } - accMap[keyString] = types.PubAccount{ - Account: acc, - PubKey: pubKey, - } - } - return -} - -// NOTE: accs must be stored in deterministic order. -func storeAccounts(eyesCli *eyes.Client, accs []types.PubAccount) { - fmt.Println("STORE ACCOUNTS", accs) - for _, acc := range accs { - accBytes := wire.BinaryBytes(acc.Account) - err := eyesCli.SetSync([]byte(acc.PubKey.KeyString()), accBytes) - if err != nil { - panic("Error storing account: " + err.Error()) - } - } -} - -//---------------------------------------- - -func allPubKeys(tx types.Tx) (pubKeys []crypto.PubKey) { - inputs := tx.GetInputs() - outputs := tx.GetOutputs() - pubKeys = make([]crypto.PubKey, 0, len(inputs)+len(outputs)) - for _, input := range inputs { - pubKeys = append(pubKeys, input.PubKey) - } - for _, output := range outputs { - pubKeys = append(pubKeys, output.PubKey) - } - return pubKeys -} diff --git a/app/validate.go b/app/validate.go new file mode 100644 index 000000000..fde0f31d6 --- /dev/null +++ b/app/validate.go @@ -0,0 +1,10 @@ +package app + +import ( + "github.com/tendermint/basecoin/types" + tmsp "github.com/tendermint/tmsp/types" +) + +func validateTx(tx types.Tx) (res tmsp.Result) { + return +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index c0b406f41..8e9753c7f 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -4,9 +4,7 @@ import ( "flag" "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" - "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" "github.com/tendermint/tmsp/server" ) @@ -15,7 +13,6 @@ func main() { addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") eyesPtr := flag.String("eyes", "tcp://0.0.0.0:46659", "MerkleEyes address") - genPtr := flag.String("genesis", "genesis.json", "Genesis JSON file") flag.Parse() // Connect to MerkleEyes @@ -27,26 +24,6 @@ func main() { // Create Basecoin app app := app.NewBasecoin(eyesCli) - // Load GenesisState - jsonBytes, err := ReadFile(*genPtr) - if err != nil { - Exit("read genesis: " + err.Error()) - } - genesisState := types.GenesisState{} - wire.ReadJSONPtr(&genesisState, jsonBytes, &err) - if err != nil { - Exit("parsing genesis JSON: " + err.Error()) - } - for _, account := range genesisState.Accounts { - // pubKeyBytes := wire.BinaryBytes(account.PubKey) - pubKeyString := account.PubKey.KeyString() - accBytes := wire.BinaryBytes(account.Account) - err = eyesCli.SetSync([]byte(pubKeyString), accBytes) - if err != nil { - Exit("loading genesis accounts: " + err.Error()) - } - } - // Start the listener svr, err := server.NewServer(*addrPtr, app) if err != nil { diff --git a/state/execution.go b/state/execution.go new file mode 100644 index 000000000..7c2e27f8b --- /dev/null +++ b/state/execution.go @@ -0,0 +1,363 @@ +package state + +import ( + "bytes" + + "github.com/tendermint/basecoin/types" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" +) + +// The accounts from the TxInputs must either already have +// crypto.PubKey.(type) != nil, (it must be known), +// or it must be specified in the TxInput. If redeclared, +// the TxInput is modified and input.PubKey set to nil. +func getInputs(state types.AccountGetter, ins []types.TxInput) (map[string]*types.Account, error) { + accounts := map[string]*types.Account{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[string(in.Address)]; ok { + return nil, types.ErrDuplicateAddress + } + acc := state.GetAccount(in.Address) + if acc == nil { + return nil, types.ErrInvalidAddress + } + // PubKey should be present in either "account" or "in" + if err := checkInputPubKey(in.Address, acc, in); err != nil { + return nil, err + } + accounts[string(in.Address)] = acc + } + return accounts, nil +} + +func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Account, outs []types.TxOutput) (map[string]*types.Account, error) { + if accounts == nil { + accounts = make(map[string]*types.Account) + } + + for _, out := range outs { + // Account shouldn't be duplicated + if _, ok := accounts[string(out.Address)]; ok { + return nil, types.ErrDuplicateAddress + } + acc := state.GetAccount(out.Address) + // output account may be nil (new) + if acc == nil { + acc = &types.Account{ + PubKey: nil, + Sequence: 0, + Balance: 0, + } + } + accounts[string(out.Address)] = acc + } + return accounts, nil +} + +// Input must not have a redundant PubKey (i.e. Account already has PubKey). +// NOTE: Account has PubKey if Sequence > 0 +func checkInputPubKey(address []byte, acc *types.Account, in types.TxInput) error { + if acc.PubKey == nil { + if in.PubKey == nil { + return types.ErrUnknownPubKey + } + if !bytes.Equal(in.PubKey.Address(), address) { + return types.ErrInvalidPubKey + } + acc.PubKey = in.PubKey + } else { + if in.PubKey != nil { + return types.ErrInvalidPubKey + } + } + return nil +} + +func validateInputs(accounts map[string]*types.Account, signBytes []byte, ins []types.TxInput) (total int64, err error) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + PanicSanity("validateInputs() expects account in accounts") + } + err = validateInput(acc, signBytes, in) + if err != nil { + return + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc *types.Account, signBytes []byte, in types.TxInput) (err error) { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { + return types.ErrInvalidSignature + } + // Check sequences + if acc.Sequence+1 != in.Sequence { + return types.ErrInvalidSequence.AppendLog(Fmt("Got %v, expected %v", in.Sequence, acc.Sequence+1)) + } + // Check amount + if acc.Balance < in.Amount { + return types.ErrInsufficientFunds + } + return nil +} + +func validateOutputs(outs []types.TxOutput) (total int64, err error) { + for _, out := range outs { + // Check TxOutput basic + if err := out.ValidateBasic(); err != nil { + return 0, err + } + // Good. Add amount to total + total += out.Amount + } + return total, nil +} + +func adjustByInputs(accounts map[string]*types.Account, ins []types.TxInput) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + PanicSanity("adjustByInputs() expects account in accounts") + } + if acc.Balance < in.Amount { + PanicSanity("adjustByInputs() expects sufficient funds") + } + acc.Balance -= in.Amount + acc.Sequence += 1 + } +} + +func adjustByOutputs(accounts map[string]*types.Account, outs []types.TxOutput) { + for _, out := range outs { + acc := accounts[string(out.Address)] + if acc == nil { + PanicSanity("adjustByOutputs() expects account in accounts") + } + acc.Balance += out.Amount + } +} + +// If the tx is invalid, an error will be returned. +// Unlike ExecBlock(), state will not be altered. +func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireable) (err error) { + + // TODO: do something with fees + fees := int64(0) + _s := blockCache.State() // hack to access validators and block height + + // Exec tx + switch tx := tx.(type) { + case *types.SendTx: + accounts, err := getInputs(blockCache, tx.Inputs) + if err != nil { + return err + } + + // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm + accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs) + if err != nil { + return err + } + + signBytes := tx.SignBytes(_s.ChainID) + inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err + } + outTotal, err := validateOutputs(tx.Outputs) + if err != nil { + return err + } + if outTotal > inTotal { + return types.ErrInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + adjustByInputs(accounts, tx.Inputs) + adjustByOutputs(accounts, tx.Outputs) + for _, acc := range accounts { + blockCache.UpdateAccount(acc) + } + + // if the evc is nil, nothing will happen + /* + if evc != nil { + for _, i := range tx.Inputs { + evc.FireEvent(types.EventStringAccInput(i.Address), types.EventDataTx{tx, nil, ""}) + } + for _, o := range tx.Outputs { + evc.FireEvent(types.EventStringAccOutput(o.Address), types.EventDataTx{tx, nil, ""}) + } + } + */ + return nil + + case *types.CallTx: + var inAcc, outAcc *types.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Info(Fmt("Can't find in account %X", tx.Input.Address)) + return types.ErrInvalidAddress + } + + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(tx.Input.Address, inAcc, tx.Input); err != nil { + log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := tx.SignBytes(_s.ChainID) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) + return err + } + if tx.Input.Amount < tx.Fee { + log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + return types.ErrInsufficientFunds + } + + if len(tx.Address) == 0 { + return types.ErrInvalidAddress.AppendLog("Address cannot be zero") + } + // Validate output + if len(tx.Address) != 20 { + log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address)) + return types.ErrInvalidAddress + } + // check if its a native contract + // XXX if IsNativeContract(tx.Address) {...} + + // Output account may be nil if we are still in mempool and contract was created in same block as this tx + // but that's fine, because the account will be created properly when the create tx runs in the block + // and then this won't return nil. otherwise, we take their fee + outAcc = blockCache.GetAccount(tx.Address) + + log.Info(Fmt("Out account: %v", outAcc)) + + // Good! + value := tx.Input.Amount - tx.Fee + inAcc.Sequence += 1 + inAcc.Balance -= tx.Fee + blockCache.UpdateAccount(inAcc) + + // The logic in runCall MUST NOT return. + if runCall { + + // VM call variables + var ( + gas int64 = tx.GasLimit + err error = nil + // caller *vm.Account = toVMAccount(inAcc) + // callee *vm.Account = nil // initialized below + // code []byte = nil + // ret []byte = nil + txCache = NewTxCache(blockCache) + /* + params = vm.Params{ + BlockHeight: int64(_s.LastBlockHeight), + BlockHash: LeftPadWord256(_s.LastBlockHash), + BlockTime: _s.LastBlockTime.Unix(), + GasLimit: _s.GetGasLimit(), + } + */ + ) + + // if you call an account that doesn't exist + // or an account with no code then we take fees (sorry pal) + // NOTE: it's fine to create a contract and call it within one + // block (nonce will prevent re-ordering of those txs) + // but to create with one contract and call with another + // you have to wait a block to avoid a re-ordering attack + // that will take your fees + if outAcc == nil { + log.Info(Fmt("%X tries to call %X but it does not exist.", tx.Input.Address, tx.Address)) + err = types.ErrInvalidAddress + goto CALL_COMPLETE + } + /* + if len(outAcc.Code) == 0 { + log.Info(Fmt("%X tries to call %X but code is blank.", inAcc.Address, tx.Address)) + err = types.ErrInvalidAddress + goto CALL_COMPLETE + } + log.Info(Fmt("Code for this contract: %X", code)) + */ + + // Run VM call and sync txCache to blockCache. + { // Capture scope for goto. + // Write caller/callee to txCache. + // txCache.UpdateAccount(caller) + // txCache.UpdateAccount(callee) + // vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, tx)) + // vmach.SetFireable(evc) + // NOTE: Call() transfers the value from caller to callee iff call succeeds. + // ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas) + if err != nil { + // Failure. Charge the gas fee. The 'value' was otherwise not transferred. + log.Info(Fmt("Error on execution: %v", err)) + goto CALL_COMPLETE + } + log.Info("Successful execution") + txCache.Sync() + } + + CALL_COMPLETE: // err may or may not be nil. + + // Create a receipt from the ret and whether errored. + // log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) + + // Fire Events for sender and receiver + // a separate event will be fired from vm for each additional call + /* + if evc != nil { + exception := "" + if err != nil { + exception = err.Error() + } + evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, ret, exception}) + evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{tx, ret, exception}) + } + */ + } else { + // The mempool does not call txs until + // the proposer determines the order of txs. + // So mempool will skip the actual .Call(), + // and only deduct from the caller's balance. + inAcc.Balance -= value + blockCache.UpdateAccount(inAcc) + } + + return nil + + default: + // binary decoding should not let this happen + PanicSanity("Unknown Tx type") + return nil + } +} + +//----------------------------------------------------------------------------- + +type InvalidTxError struct { + Tx types.Tx + Reason error +} + +func (txErr InvalidTxError) Error() string { + return Fmt("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason) +} diff --git a/state/log.go b/state/log.go new file mode 100644 index 000000000..5b102b570 --- /dev/null +++ b/state/log.go @@ -0,0 +1,7 @@ +package state + +import ( + "github.com/tendermint/go-logger" +) + +var log = logger.New("module", "state") diff --git a/state/state.go b/state/state.go new file mode 100644 index 000000000..750bc9017 --- /dev/null +++ b/state/state.go @@ -0,0 +1,48 @@ +package state + +import ( + "github.com/tendermint/basecoin/types" + "github.com/tendermint/go-wire" + eyes "github.com/tendermint/merkleeyes/client" +) + +type State struct { + chainID string + eyesCli *eyes.Client + + LastBlockHeight uint64 + LastBlockHash []byte + GasLimit int64 +} + +func NewState(chainID string, eyesCli *eyes.Client) *State { + s := &State{ + chainID: chainID, + eyesCli: eyesCli, + } + return s +} + +func (s *State) GetAccount(addr []byte) *types.Account { + accBytes, err := s.eyesCli.GetSync(addr) + if err != nil { + panic("Error loading account: " + err.Error()) + } + if len(accBytes) == 0 { + return nil + } + var acc types.Account + err = wire.ReadBinaryBytes(accBytes, &acc) + if err != nil { + panic("Error reading account: " + err.Error()) + } + return &acc +} + +func (s *State) SetAccount(acc *types.Account) { + accBytes := wire.BinaryBytes(acc) + err := s.eyesCli.SetSync(acc.PubKey.Address(), accBytes) + if err != nil { + panic("Error storing account: " + err.Error()) + } +} diff --git a/tests/common.go b/tests/common.go index 4e4608a20..3f9e8e5e8 100644 --- a/tests/common.go +++ b/tests/common.go @@ -12,8 +12,8 @@ func PrivAccountFromSecret(secret string) types.PrivAccount { privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret)) privAccount := types.PrivAccount{ PrivKey: privKey, - PubKey: privKey.PubKey(), Account: types.Account{ + PubKey: privKey.PubKey(), Sequence: 0, Balance: 0, }, @@ -22,21 +22,21 @@ func PrivAccountFromSecret(secret string) types.PrivAccount { } // Make `num` random accounts -func RandAccounts(num int, minAmount uint64, maxAmount uint64) []types.PrivAccount { +func RandAccounts(num int, minAmount int64, maxAmount int64) []types.PrivAccount { privAccs := make([]types.PrivAccount, num) for i := 0; i < num; i++ { balance := minAmount if maxAmount > minAmount { - balance += RandUint64() % (maxAmount - minAmount) + balance += RandInt64() % (maxAmount - minAmount) } privKey := crypto.GenPrivKeyEd25519() pubKey := privKey.PubKey() privAccs[i] = types.PrivAccount{ PrivKey: privKey, - PubKey: pubKey, Account: types.Account{ + PubKey: pubKey, Sequence: 0, Balance: balance, }, diff --git a/tests/tendermint/main.go b/tests/tendermint/main.go index b4f11f91c..6f669cdbc 100644 --- a/tests/tendermint/main.go +++ b/tests/tendermint/main.go @@ -35,7 +35,7 @@ func main() { // Get the root account root := tests.PrivAccountFromSecret("root") - sequence := uint(0) + sequence := int(0) // Make a bunch of PrivAccounts privAccounts := tests.RandAccounts(1000, 1000000, 0) privAccountSequences := make(map[string]int) @@ -44,17 +44,18 @@ func main() { for i := 0; i < len(privAccounts); i++ { privAccount := privAccounts[i] tx := &types.SendTx{ - Inputs: []types.Input{ - types.Input{ - PubKey: root.PubKey, + Inputs: []types.TxInput{ + types.TxInput{ + Address: root.Account.PubKey.Address(), + PubKey: root.Account.PubKey, // TODO is this needed? Amount: 1000002, Sequence: sequence, }, }, - Outputs: []types.Output{ - types.Output{ - PubKey: privAccount.PubKey, - Amount: 1000000, + Outputs: []types.TxOutput{ + types.TxOutput{ + Address: privAccount.Account.PubKey.Address(), + Amount: 1000000, }, }, } @@ -89,22 +90,23 @@ func main() { } privAccountA := privAccounts[randA] - privAccountASequence := privAccountSequences[privAccountA.PubKey.KeyString()] - privAccountSequences[privAccountA.PubKey.KeyString()] = privAccountASequence + 1 + privAccountASequence := privAccountSequences[privAccountA.Account.PubKey.KeyString()] + privAccountSequences[privAccountA.Account.PubKey.KeyString()] = privAccountASequence + 1 privAccountB := privAccounts[randB] tx := &types.SendTx{ - Inputs: []types.Input{ - types.Input{ - PubKey: privAccountA.PubKey, + Inputs: []types.TxInput{ + types.TxInput{ + Address: privAccountA.Account.PubKey.Address(), + PubKey: privAccountA.Account.PubKey, Amount: 3, - Sequence: uint(privAccountASequence), + Sequence: privAccountASequence, }, }, - Outputs: []types.Output{ - types.Output{ - PubKey: privAccountB.PubKey, - Amount: 1, + Outputs: []types.TxOutput{ + types.TxOutput{ + Address: privAccountB.Account.PubKey.Address(), + Amount: 1, }, }, } diff --git a/types/account.go b/types/account.go new file mode 100644 index 000000000..9d8e6bace --- /dev/null +++ b/types/account.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" + + "github.com/tendermint/go-crypto" +) + +type Account struct { + PubKey crypto.PubKey // May be nil, if not known. + Sequence int + Balance int64 +} + +func (acc *Account) Copy() *Account { + accCopy := *acc + return &accCopy +} + +func (acc *Account) String() string { + if acc == nil { + return "nil-Account" + } + return fmt.Sprintf("Account{%v %v %v}", + acc.PubKey, acc.Sequence, acc.Balance) +} + +//---------------------------------------- + +type PrivAccount struct { + crypto.PrivKey + Account +} + +//---------------------------------------- + +type AccountGetter interface { + GetAccount(addr []byte) *Account +} + +type AccountGetterSetter interface { + GetAccount(addr []byte) *Account + SetAccount(acc *Account) +} diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 000000000..534b11c16 --- /dev/null +++ b/types/errors.go @@ -0,0 +1,21 @@ +package types + +import ( + tmsp "github.com/tendermint/tmsp/types" +) + +var ( + ErrDuplicateAddress = tmsp.NewError(tmsp.CodeType_BaseDuplicateAddress, "Error duplicate address") + ErrEncodingError = tmsp.NewError(tmsp.CodeType_BaseEncodingError, "Error encoding error") + ErrInsufficientFees = tmsp.NewError(tmsp.CodeType_BaseInsufficientFees, "Error insufficient fees") + ErrInsufficientFunds = tmsp.NewError(tmsp.CodeType_BaseInsufficientFunds, "Error insufficient funds") + ErrInsufficientGasPrice = tmsp.NewError(tmsp.CodeType_BaseInsufficientGasPrice, "Error insufficient gas price") + ErrInvalidAddress = tmsp.NewError(tmsp.CodeType_BaseInvalidAddress, "Error invalid address") + ErrInvalidAmount = tmsp.NewError(tmsp.CodeType_BaseInvalidAmount, "Error invalid amount") + ErrInvalidPubKey = tmsp.NewError(tmsp.CodeType_BaseInvalidPubKey, "Error invalid pubkey") + ErrInvalidSequence = tmsp.NewError(tmsp.CodeType_BaseInvalidSequence, "Error invalid sequence") + ErrInvalidSignature = tmsp.NewError(tmsp.CodeType_BaseInvalidSignature, "Error invalid signature") + ErrUnknownPubKey = tmsp.NewError(tmsp.CodeType_BaseUnknownPubKey, "Error unknown pubkey") + + ResultOK = tmsp.NewResultOK(nil, "") +) diff --git a/types/native.go b/types/native.go new file mode 100644 index 000000000..7f0f9048e --- /dev/null +++ b/types/native.go @@ -0,0 +1,6 @@ +package types + +type Plugin func(ags AccountGetterSetter, + caller *Account, + input []byte, + gas *int64) (result []byte, err error) diff --git a/types/tx.go b/types/tx.go new file mode 100644 index 000000000..1835a3077 --- /dev/null +++ b/types/tx.go @@ -0,0 +1,174 @@ +package types + +import ( + "bytes" + "encoding/json" + + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" + "golang.org/x/crypto/ripemd160" +) + +/* +Tx (Transaction) is an atomic operation on the ledger state. + +Account Types: + - SendTx Send coins to address + - CallTx Send a msg to a contract that runs in the vm +*/ + +type Tx interface { + SignBytes(chainID string) []byte +} + +// Types of Tx implementations +const ( + // Account transactions + TxTypeSend = byte(0x01) + TxTypeCall = byte(0x02) +) + +var _ = wire.RegisterInterface( + struct{ Tx }{}, + wire.ConcreteType{&SendTx{}, TxTypeSend}, + wire.ConcreteType{&CallTx{}, TxTypeCall}, +) + +//----------------------------------------------------------------------------- + +type TxInput struct { + Address []byte `json:"address"` // Hash of the PubKey + Amount int64 `json:"amount"` // Must not exceed account balance + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx + PubKey crypto.PubKey `json:"pub_key"` // May be nil +} + +func (txIn TxInput) ValidateBasic() error { + if len(txIn.Address) != 20 { + return ErrInvalidAddress + } + if txIn.Amount == 0 { + return ErrInvalidAmount + } + return nil +} + +func (txIn TxInput) SignBytes() []byte { + return []byte(Fmt(`{"address":"%X","amount":%v,"sequence":%v}`, + txIn.Address, txIn.Amount, txIn.Sequence)) +} + +func (txIn TxInput) String() string { + return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PubKey) +} + +//----------------------------------------------------------------------------- + +type TxOutput struct { + Address []byte `json:"address"` // Hash of the PubKey + Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs. +} + +func (txOut TxOutput) ValidateBasic() error { + if len(txOut.Address) != 20 { + return ErrInvalidAddress + } + if txOut.Amount == 0 { + return ErrInvalidAmount + } + return nil +} + +func (txOut TxOutput) SignBytes() []byte { + return []byte(Fmt(`{"address":"%X","amount":%v}`, + txOut.Address, txOut.Amount)) +} + +func (txOut TxOutput) String() string { + return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Amount) +} + +//----------------------------------------------------------------------------- + +type SendTx struct { + Inputs []TxInput `json:"inputs"` + Outputs []TxOutput `json:"outputs"` +} + +func (tx *SendTx) SignBytes(chainID string) []byte { + var buf = new(bytes.Buffer) + buf.Write([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID)))) + buf.Write([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeSend))) + for i, in := range tx.Inputs { + buf.Write(in.SignBytes()) + if i != len(tx.Inputs)-1 { + buf.Write([]byte(",")) + } + } + buf.Write([]byte(`],"outputs":[`)) + for i, out := range tx.Outputs { + buf.Write(out.SignBytes()) + if i != len(tx.Outputs)-1 { + buf.Write([]byte(",")) + } + } + buf.Write([]byte(`]}]}`)) + return buf.Bytes() +} + +func (tx *SendTx) String() string { + return Fmt("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) +} + +//----------------------------------------------------------------------------- + +type CallTx struct { + Input TxInput `json:"input"` + Address []byte `json:"address"` + GasLimit int64 `json:"gas_limit"` + Fee int64 `json:"fee"` + Data []byte `json:"data"` +} + +func (tx *CallTx) SignBytes(chainID string) []byte { + var buf = new(bytes.Buffer) + buf.Write([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID)))) + buf.Write([]byte(Fmt(`,"tx":[%v,{"address":"%X","data":"%X"`, TxTypeCall, tx.Address, tx.Data))) + buf.Write([]byte(Fmt(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit))) + buf.Write(tx.Input.SignBytes()) + buf.Write([]byte(`}]}`)) + return buf.Bytes() +} + +func (tx *CallTx) String() string { + return Fmt("CallTx{%v -> %x: %x}", tx.Input, tx.Address, tx.Data) +} + +func NewContractAddress(caller []byte, nonce int) []byte { + temp := make([]byte, 32+8) + copy(temp, caller) + PutInt64BE(temp[32:], int64(nonce)) + hasher := ripemd160.New() + hasher.Write(temp) // does not error + return hasher.Sum(nil) +} + +//----------------------------------------------------------------------------- + +func TxID(chainID string, tx Tx) []byte { + signBytes := tx.SignBytes(chainID) + return wire.BinaryRipemd160(signBytes) +} + +//-------------------------------------------------------------------------------- + +// Contract: This function is deterministic and completely reversible. +func jsonEscape(str string) string { + escapedBytes, err := json.Marshal(str) + if err != nil { + PanicSanity(Fmt("Error json-escaping a string", str)) + } + return string(escapedBytes) +} diff --git a/types/tx_test.go b/types/tx_test.go new file mode 100644 index 000000000..20ebf6ac2 --- /dev/null +++ b/types/tx_test.go @@ -0,0 +1,64 @@ +package types + +import ( + "testing" + + . "github.com/tendermint/go-common" +) + +var chainID string = "test_chain" + +func TestSendTxSignable(t *testing.T) { + sendTx := &SendTx{ + Inputs: []TxInput{ + TxInput{ + Address: []byte("input1"), + Amount: 12345, + Sequence: 67890, + }, + TxInput{ + Address: []byte("input2"), + Amount: 111, + Sequence: 222, + }, + }, + Outputs: []TxOutput{ + TxOutput{ + Address: []byte("output1"), + Amount: 333, + }, + TxOutput{ + Address: []byte("output2"), + Amount: 444, + }, + }, + } + signBytes := sendTx.SignBytes(chainID) + signStr := string(signBytes) + expected := Fmt(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, + chainID) + if signStr != expected { + t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) + } +} + +func TestCallTxSignable(t *testing.T) { + callTx := &CallTx{ + Input: TxInput{ + Address: []byte("input1"), + Amount: 12345, + Sequence: 67890, + }, + Address: []byte("contract1"), + GasLimit: 111, + Fee: 222, + Data: []byte("data1"), + } + signBytes := callTx.SignBytes(chainID) + signStr := string(signBytes) + expected := Fmt(`{"chain_id":"%s","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`, + chainID) + if signStr != expected { + t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) + } +} diff --git a/types/types.go b/types/types.go deleted file mode 100644 index 27c51127a..000000000 --- a/types/types.go +++ /dev/null @@ -1,100 +0,0 @@ -package types - -import ( - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" - gov "github.com/tendermint/governmint/types" -) - -type Input struct { - PubKey crypto.PubKey - Amount uint64 - Sequence uint - Signature crypto.Signature -} - -type Output struct { - PubKey crypto.PubKey - Amount uint64 -} - -type SendTx struct { - Inputs []Input - Outputs []Output -} - -func (tx *SendTx) SignBytes() []byte { - sigs := make([]crypto.Signature, len(tx.Inputs)) - for i, input := range tx.Inputs { - sigs[i] = input.Signature - input.Signature = nil - tx.Inputs[i] = input - } - signBytes := wire.BinaryBytes(tx) - for i := range tx.Inputs { - tx.Inputs[i].Signature = sigs[i] - } - return signBytes -} - -func (tx *SendTx) GetInputs() []Input { return tx.Inputs } -func (tx *SendTx) GetOutputs() []Output { return tx.Outputs } - -type GovTx struct { - Input Input - Tx gov.Tx -} - -func (tx *GovTx) SignBytes() []byte { - sig := tx.Input.Signature - tx.Input.Signature = nil - signBytes := wire.BinaryBytes(tx) - tx.Input.Signature = sig - return signBytes -} - -func (tx *GovTx) GetInputs() []Input { return []Input{tx.Input} } -func (tx *GovTx) GetOutputs() []Output { return nil } - -type Tx interface { - AssertIsTx() - SignBytes() []byte - GetInputs() []Input - GetOutputs() []Output -} - -func (_ *SendTx) AssertIsTx() {} -func (_ *GovTx) AssertIsTx() {} - -const ( - TxTypeSend = byte(0x01) - TxTypeGov = byte(0x02) -) - -var _ = wire.RegisterInterface( - struct{ Tx }{}, - wire.ConcreteType{&SendTx{}, TxTypeSend}, - wire.ConcreteType{&GovTx{}, TxTypeGov}, -) - -//---------------------------------------- - -type Account struct { - Sequence uint - Balance uint64 -} - -type PubAccount struct { - crypto.PubKey - Account -} - -type PrivAccount struct { - crypto.PubKey - crypto.PrivKey - Account -} - -type GenesisState struct { - Accounts []PubAccount -}