// Returns whether +2/3 have prevoted/committed for BlockHash. func (pol *POL) Verify(valSet *sm.ValidatorSet) error { if uint(len(pol.Votes)) != valSet.Size() { return fmt.Errorf("Invalid POL votes count: Expected %v, got %v", valSet.Size(), len(pol.Votes)) } talliedVotingPower := uint64(0) prevoteDoc := account.SignBytes(&types.Vote{ Height: pol.Height, Round: pol.Round, Type: types.VoteTypePrevote, BlockHash: pol.BlockHash, BlockParts: pol.BlockParts, }) seenValidators := map[string]struct{}{} for idx, vote := range pol.Votes { // vote may be zero, in which case skip. if vote.Signature.IsZero() { continue } voteDoc := prevoteDoc _, val := valSet.GetByIndex(uint(idx)) // Commit vote? if vote.Round < pol.Round { voteDoc = account.SignBytes(&types.Vote{ Height: pol.Height, Round: vote.Round, Type: types.VoteTypeCommit, BlockHash: pol.BlockHash, BlockParts: pol.BlockParts, }) } else if vote.Round > pol.Round { return fmt.Errorf("Invalid commit round %v for POL %v", vote.Round, pol) } // Validate if _, seen := seenValidators[string(val.Address)]; seen { return fmt.Errorf("Duplicate validator for vote %v for POL %v", vote, pol) } if !val.PubKey.VerifyBytes(voteDoc, vote.Signature) { return fmt.Errorf("Invalid signature for vote %v for POL %v", vote, pol) } // Tally seenValidators[string(val.Address)] = struct{}{} talliedVotingPower += val.VotingPower } if talliedVotingPower > valSet.TotalVotingPower()*2/3 { return nil } else { return fmt.Errorf("Invalid POL, insufficient voting power %v, needed %v", talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) } }
func (cs *ConsensusState) SetProposal(proposal *Proposal) error { cs.mtx.Lock() defer cs.mtx.Unlock() // Already have one if cs.Proposal != nil { return nil } // Does not apply if proposal.Height != cs.Height || proposal.Round != cs.Round { return nil } // We don't care about the proposal if we're already in RoundStepCommit. if cs.Step == RoundStepCommit { return nil } // Verify signature if !cs.Validators.Proposer().PubKey.VerifyBytes(account.SignBytes(proposal), proposal.Signature) { return ErrInvalidProposalSignature } cs.Proposal = proposal cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockParts) cs.ProposalPOLParts = types.NewPartSetFromHeader(proposal.POLParts) return nil }
func (cs *ConsensusState) SetProposal(proposal *types.Proposal) error { cs.mtx.Lock() defer cs.mtx.Unlock() // Already have one if cs.Proposal != nil { return nil } // Does not apply if proposal.Height != cs.Height || proposal.Round != cs.Round { return nil } // We don't care about the proposal if we're already in RoundStepCommit. if RoundStepCommit <= cs.Step { return nil } // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. if proposal.POLRound != -1 && (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { return ErrInvalidProposalPOLRound } // Verify signature if !cs.Validators.Proposer().PubKey.VerifyBytes(acm.SignBytes(cs.state.ChainID, proposal), proposal.Signature) { return ErrInvalidProposalSignature } cs.Proposal = proposal cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) return nil }
// True if added, false if not. // Returns ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] // NOTE: vote should not be mutated after adding. // Returns the validator index of the vote unless error is set. func (voteSet *VoteSet) Add(address []byte, vote *types.Vote) (bool, uint, error) { voteSet.mtx.Lock() defer voteSet.mtx.Unlock() // Make sure the step matches. (or that vote is commit && round < voteSet.round) if vote.Height != voteSet.height || (vote.Type != types.VoteTypeCommit && vote.Round != voteSet.round) || (vote.Type != types.VoteTypeCommit && vote.Type != voteSet.type_) || (vote.Type == types.VoteTypeCommit && voteSet.type_ != types.VoteTypeCommit && vote.Round >= voteSet.round) { return false, 0, types.ErrVoteUnexpectedStep } // Ensure that signer is a validator. valIndex, val := voteSet.valSet.GetByAddress(address) if val == nil { return false, 0, types.ErrVoteInvalidAccount } // Check signature. if !val.PubKey.VerifyBytes(account.SignBytes(vote), vote.Signature) { // Bad signature. return false, 0, types.ErrVoteInvalidSignature } return voteSet.addVote(valIndex, vote) }
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 := account.SignBytes(sendTx) signStr := string(signBytes) expected := Fmt(`{"network":"%X","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, config.GetString("network")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func TestBondTxSignable(t *testing.T) { privAccount := account.GenPrivAccountFromKey([64]byte{}) bondTx := &BondTx{ PubKey: privAccount.PubKey.(account.PubKeyEd25519), Inputs: []*TxInput{ &TxInput{ Address: []byte("input1"), Amount: 12345, Sequence: 67890, }, &TxInput{ Address: []byte("input2"), Amount: 111, Sequence: 222, }, }, UnbondTo: []*TxOutput{ &TxOutput{ Address: []byte("output1"), Amount: 333, }, &TxOutput{ Address: []byte("output2"), Amount: 444, }, }, } signBytes := account.SignBytes(bondTx) signStr := string(signBytes) expected := Fmt(`{"network":"%X","tx":[17,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"pub_key":[1,"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29"],"unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, config.GetString("network")) if signStr != expected { t.Errorf("Got unexpected sign string for BondTx") } }
func (data *Data) Hash() []byte { if data.hash == nil { bs := make([]interface{}, len(data.Txs)) for i, tx := range data.Txs { bs[i] = acm.SignBytes(config.GetString("chain_id"), tx) } data.hash = merkle.SimpleHashFromBinaries(bs) // NOTE: leaves are TxIDs. } return data.hash }
func (data *Data) Hash() []byte { if data.hash == nil { bs := make([]interface{}, len(data.Txs)) for i, tx := range data.Txs { bs[i] = account.SignBytes(tx) } data.hash = merkle.HashFromBinaries(bs) } return data.hash }
func TestRebondTxSignable(t *testing.T) { rebondTx := &RebondTx{ Address: []byte("address1"), Height: 111, } signBytes := account.SignBytes(rebondTx) signStr := string(signBytes) expected := Fmt(`{"network":"%X","tx":[19,{"address":"6164647265737331","height":111}]}`, config.GetString("network")) if signStr != expected { t.Errorf("Got unexpected sign string for RebondTx") } }
func TestUnbondTxSignable(t *testing.T) { unbondTx := &UnbondTx{ Address: []byte("address1"), Height: 111, } signBytes := acm.SignBytes(chainID, unbondTx) signStr := string(signBytes) expected := Fmt(`{"chain_id":"%s","tx":[18,{"address":"6164647265737331","height":111}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for UnbondTx") } }
// Verify that +2/3 of the set had signed the given signBytes func (valSet *ValidatorSet) VerifyValidation(chainID string, hash []byte, parts PartSetHeader, height int, v *Validation) error { if valSet.Size() != len(v.Precommits) { return fmt.Errorf("Invalid validation -- wrong set size: %v vs %v", valSet.Size(), len(v.Precommits)) } if height != v.Height() { return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, v.Height()) } talliedVotingPower := int64(0) round := v.Round() for idx, precommit := range v.Precommits { // may be nil if validator skipped. if precommit == nil { continue } if precommit.Height != height { return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, precommit.Height) } if precommit.Round != round { return fmt.Errorf("Invalid validation -- wrong round: %v vs %v", round, precommit.Round) } if precommit.Type != VoteTypePrecommit { return fmt.Errorf("Invalid validation -- not precommit @ index %v", idx) } _, val := valSet.GetByIndex(idx) // Validate signature precommitSignBytes := account.SignBytes(chainID, precommit) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { return fmt.Errorf("Invalid validation -- invalid signature: %v", precommit) } if !bytes.Equal(precommit.BlockHash, hash) { continue // Not an error, but doesn't count } if !parts.Equals(precommit.BlockPartsHeader) { continue // Not an error, but doesn't count } // Good precommit! talliedVotingPower += val.VotingPower } if talliedVotingPower > valSet.TotalVotingPower()*2/3 { return nil } else { return fmt.Errorf("Invalid validation -- insufficient voting power: got %v, needed %v", talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) } }
func TestProposalSignable(t *testing.T) { proposal := &Proposal{ Height: 12345, Round: 23456, BlockParts: types.PartSetHeader{111, []byte("blockparts")}, POLParts: types.PartSetHeader{222, []byte("polparts")}, Signature: nil, } signBytes := account.SignBytes(proposal) signStr := string(signBytes) expected := Fmt(`{"network":"%X","proposal":{"block_parts":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_parts":{"hash":"706F6C7061727473","total":222},"round":23456}}`, config.GetString("network")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func TestProposalSignable(t *testing.T) { proposal := &Proposal{ Height: 12345, Round: 23456, BlockPartsHeader: PartSetHeader{111, []byte("blockparts")}, POLRound: -1, } signBytes := acm.SignBytes(config.GetString("chain_id"), proposal) signStr := string(signBytes) expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_round":-1,"round":23456}}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func (voteSet *VoteSet) addVote(val *Validator, valIndex int, vote *Vote) (bool, int, error) { // Make sure the step matches. (or that vote is commit && round < voteSet.round) if (vote.Height != voteSet.height) || (vote.Round != voteSet.round) || (vote.Type != voteSet.type_) { return false, 0, ErrVoteUnexpectedStep } // Check signature. if !val.PubKey.VerifyBytes(acm.SignBytes(config.GetString("chain_id"), vote), vote.Signature) { // Bad signature. return false, 0, ErrVoteInvalidSignature } // If vote already exists, return false. if existingVote := voteSet.votes[valIndex]; existingVote != nil { if bytes.Equal(existingVote.BlockHash, vote.BlockHash) { return false, valIndex, nil } else { return false, valIndex, &ErrVoteConflictingSignature{ VoteA: existingVote, VoteB: vote, } } } // Add vote. voteSet.votes[valIndex] = vote voteSet.votesBitArray.SetIndex(valIndex, true) blockKey := string(vote.BlockHash) + string(wire.BinaryBytes(vote.BlockPartsHeader)) totalBlockHashVotes := voteSet.votesByBlock[blockKey] + val.VotingPower voteSet.votesByBlock[blockKey] = totalBlockHashVotes voteSet.totalVotes += val.VotingPower // If we just nudged it up to two thirds majority, add it. if totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3 && (totalBlockHashVotes-val.VotingPower) <= voteSet.valSet.TotalVotingPower()*2/3 { voteSet.maj23Hash = vote.BlockHash voteSet.maj23PartsHeader = vote.BlockPartsHeader voteSet.maj23Exists = true } return true, valIndex, nil }
func (privVal *PrivValidator) SignRebondTx(rebondTx *types.RebondTx) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() if privVal.LastHeight < rebondTx.Height { // Persist height/round/step privVal.LastHeight = rebondTx.Height privVal.LastRound = math.MaxUint64 // We can't do anything else for this rebondTx.Height. privVal.LastStep = math.MaxUint8 privVal.save() // Sign rebondTx.Signature = privVal.PrivKey.Sign(account.SignBytes(rebondTx)).(account.SignatureEd25519) return nil } else { return errors.New(fmt.Sprintf("Attempt of duplicate signing of rebondTx: Height %v", rebondTx.Height)) } }
func checkTx(t *testing.T, fromAddr []byte, priv *acm.PrivAccount, tx *types.SendTx) { if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 { t.Fatal("Tx input addresses don't match!") } signBytes := acm.SignBytes(chainID, tx) in := tx.Inputs[0] //(*types.SendTx).Inputs[0] if err := in.ValidateBasic(); err != nil { t.Fatal(err) } // Check signatures // acc := getAccount(t, byteAddr) // NOTE: using the acc here instead of the in fails; it is nil. if !in.PubKey.VerifyBytes(signBytes, in.Signature) { t.Fatal(types.ErrTxInvalidSignature) } }
func TestNameTxSignable(t *testing.T) { nameTx := &NameTx{ Input: &TxInput{ Address: []byte("input1"), Amount: 12345, Sequence: 250, }, Name: "google.com", Data: "secretly.not.google.com", Fee: 1000, } signBytes := acm.SignBytes(chainID, nameTx) signStr := string(signBytes) expected := Fmt(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"696E70757431","amount":12345,"sequence":250},"name":"google.com"}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func (privVal *PrivValidator) SignRebondTx(chainID string, rebondTx *RebondTx) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() if privVal.LastHeight < rebondTx.Height { // Persist height/round/step // Prevent doing anything else for this rebondTx.Height. privVal.LastHeight = rebondTx.Height privVal.LastRound = math.MaxInt32 // MaxInt64 overflows on 32bit architectures. privVal.LastStep = math.MaxInt8 privVal.save() // Sign rebondTx.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, rebondTx)).(acm.SignatureEd25519) return nil } else { return errors.New(fmt.Sprintf("Attempt of duplicate signing of rebondTx: Height %v", rebondTx.Height)) } }
// Verify that +2/3 of the set had signed the given signBytes func (valSet *ValidatorSet) VerifyValidation(hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error { if valSet.Size() != uint(len(v.Commits)) { return errors.New(Fmt("Invalid validation -- wrong set size: %v vs %v", valSet.Size(), len(v.Commits))) } talliedVotingPower := uint64(0) seenValidators := map[string]struct{}{} for idx, commit := range v.Commits { // may be zero, in which case skip. if commit.Signature.IsZero() { continue } _, val := valSet.GetByIndex(uint(idx)) commitSignBytes := account.SignBytes(&types.Vote{ Height: height, Round: commit.Round, Type: types.VoteTypeCommit, BlockHash: hash, BlockParts: parts, }) // Validate if _, seen := seenValidators[string(val.Address)]; seen { return fmt.Errorf("Duplicate validator for commit %v for Validation %v", commit, v) } if !val.PubKey.VerifyBytes(commitSignBytes, commit.Signature) { return fmt.Errorf("Invalid signature for commit %v for Validation %v", commit, v) } // Tally seenValidators[string(val.Address)] = struct{}{} talliedVotingPower += val.VotingPower } if talliedVotingPower > valSet.TotalVotingPower()*2/3 { return nil } else { return fmt.Errorf("insufficient voting power %v, needed %v", talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) } }
func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() if privVal.LastHeight < proposal.Height || privVal.LastHeight == proposal.Height && privVal.LastRound < proposal.Round || privVal.LastHeight == 0 && privVal.LastRound == 0 && privVal.LastStep == stepNone { // Persist height/round/step privVal.LastHeight = proposal.Height privVal.LastRound = proposal.Round privVal.LastStep = stepPropose privVal.save() // Sign proposal.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, proposal)).(acm.SignatureEd25519) return nil } else { return errors.New(fmt.Sprintf("Attempt of duplicate signing of proposal: Height %v, Round %v", proposal.Height, proposal.Round)) } }
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 := account.SignBytes(callTx) signStr := string(signBytes) expected := Fmt(`{"network":"%X","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`, config.GetString("network")) if signStr != expected { t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func TestPermissionsTxSignable(t *testing.T) { permsTx := &PermissionsTx{ Input: &TxInput{ Address: []byte("input1"), Amount: 12345, Sequence: 250, }, PermArgs: &ptypes.SetBaseArgs{ Address: []byte("address1"), Permission: 1, Value: true, }, } signBytes := acm.SignBytes(chainID, permsTx) signStr := string(signBytes) expected := Fmt(`{"chain_id":"%s","tx":[32,{"args":"[2,{"address":"6164647265737331","permission":1,"value":true}]","input":{"address":"696E70757431","amount":12345,"sequence":250}}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) } }
func TestBondTxSignable(t *testing.T) { privKeyBytes := make([]byte, 64) privAccount := acm.GenPrivAccountFromPrivKeyBytes(privKeyBytes) bondTx := &BondTx{ PubKey: privAccount.PubKey.(acm.PubKeyEd25519), Inputs: []*TxInput{ &TxInput{ Address: []byte("input1"), Amount: 12345, Sequence: 67890, }, &TxInput{ Address: []byte("input2"), Amount: 111, Sequence: 222, }, }, UnbondTo: []*TxOutput{ &TxOutput{ Address: []byte("output1"), Amount: 333, }, &TxOutput{ Address: []byte("output2"), Amount: 444, }, }, } signBytes := acm.SignBytes(chainID, bondTx) signStr := string(signBytes) expected := Fmt(`{"chain_id":"%s","tx":[17,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"pub_key":[1,"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29"],"unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Unexpected sign string for BondTx. \nGot %s\nExpected %s", signStr, expected) } }
func TxId(tx Tx) []byte { signBytes := account.SignBytes(tx) return binary.BinaryRipemd160(signBytes) }
func (privVal *PrivValidator) SignVoteUnsafe(vote *types.Vote) { vote.Signature = privVal.PrivKey.Sign(account.SignBytes(vote)).(account.SignatureEd25519) }
// 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 } // ensure all inputs have send permissions if !hasSendPermission(blockCache, accounts) { return fmt.Errorf("At least one input lacks permission for SendTx") } // 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 := acm.SignBytes(_s.ChainID, tx) 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.ErrTxInsufficientFunds } 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 *acm.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.ErrTxInvalidAddress } createContract := len(tx.Address) == 0 if createContract { if !hasCreateContractPermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address) } } else { if !hasCallPermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) } } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address)) return err } signBytes := acm.SignBytes(_s.ChainID, tx) 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.ErrTxInsufficientFunds } if !createContract { // Validate output if len(tx.Address) != 20 { log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address)) return types.ErrTxInvalidAddress } // check if its a native contract if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)") } // 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 !createContract && (outAcc == nil || len(outAcc.Code) == 0) { // 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.", inAcc.Address, tx.Address)) } else { log.Info(Fmt("%X tries to call %X but code is blank.", inAcc.Address, tx.Address)) } err = types.ErrTxInvalidAddress goto CALL_COMPLETE } // get or create callee if createContract { // We already checked for permission callee = txCache.CreateAccount(caller) log.Info(Fmt("Created new contract %X", callee.Address)) code = tx.Data } else { callee = toVMAccount(outAcc) log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code)) code = callee.Code } 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") if createContract { callee.Code = ret } 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 if createContract { inAcc.Sequence += 1 } blockCache.UpdateAccount(inAcc) } return nil case *types.NameTx: var inAcc *acm.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.ErrTxInvalidAddress } // check permission if !hasNamePermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address) } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address)) return err } signBytes := acm.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) return err } // fee is in addition to the amount which is used to determine the TTL if tx.Input.Amount < tx.Fee { log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) return types.ErrTxInsufficientFunds } // validate the input strings if err := tx.ValidateStrings(); err != nil { return err } value := tx.Input.Amount - tx.Fee // let's say cost of a name for one block is len(data) + 32 costPerBlock := types.NameCostPerBlock(types.NameBaseCost(tx.Name, tx.Data)) expiresIn := int(value / costPerBlock) lastBlockHeight := _s.LastBlockHeight log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight) // check if the name exists entry := blockCache.GetNameRegEntry(tx.Name) if entry != nil { var expired bool // if the entry already exists, and hasn't expired, we must be owner if entry.Expires > lastBlockHeight { // ensure we are owner if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) return types.ErrTxPermissionDenied } } else { expired = true } // no value and empty data means delete the entry if value == 0 && len(tx.Data) == 0 { // maybe we reward you for telling us we can delete this crap // (owners if not expired, anyone if expired) log.Info("Removing namereg entry", "name", entry.Name) blockCache.RemoveNameRegEntry(entry.Name) } else { // update the entry by bumping the expiry // and changing the data if expired { if expiresIn < types.MinNameRegistrationPeriod { return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod)) } entry.Expires = lastBlockHeight + expiresIn entry.Owner = tx.Input.Address log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner) } else { // since the size of the data may have changed // we use the total amount of "credit" oldCredit := int64(entry.Expires-lastBlockHeight) * types.NameBaseCost(entry.Name, entry.Data) credit := oldCredit + value expiresIn = int(credit / costPerBlock) if expiresIn < types.MinNameRegistrationPeriod { return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod)) } entry.Expires = lastBlockHeight + expiresIn log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit) } entry.Data = tx.Data blockCache.UpdateNameRegEntry(entry) } } else { if expiresIn < types.MinNameRegistrationPeriod { return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod)) } // entry does not exist, so create it entry = &types.NameRegEntry{ Name: tx.Name, Owner: tx.Input.Address, Data: tx.Data, Expires: lastBlockHeight + expiresIn, } log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn) blockCache.UpdateNameRegEntry(entry) } // TODO: something with the value sent? // Good! inAcc.Sequence += 1 inAcc.Balance -= value blockCache.UpdateAccount(inAcc) // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? if evc != nil { evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""}) evc.FireEvent(types.EventStringNameReg(tx.Name), types.EventDataTx{tx, nil, ""}) } return nil case *types.BondTx: valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) if valInfo != nil { // TODO: In the future, check that the validator wasn't destroyed, // add funds, merge UnbondTo outputs, and unbond validator. return errors.New("Adding coins to existing validators not yet supported") } 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 // though outputs aren't created until unbonding/release time canCreate := hasCreateAccountPermission(blockCache, accounts) for _, out := range tx.UnbondTo { acc := blockCache.GetAccount(out.Address) if acc == nil && !canCreate { return fmt.Errorf("At least one input does not have permission to create accounts") } } bondAcc := blockCache.GetAccount(tx.PubKey.Address()) if !hasBondPermission(blockCache, bondAcc) { return fmt.Errorf("The bonder does not have permission to bond") } if !hasBondOrSendPermission(blockCache, accounts) { return fmt.Errorf("At least one input lacks permission to bond") } signBytes := acm.SignBytes(_s.ChainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err } if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) { return types.ErrTxInvalidSignature } outTotal, err := validateOutputs(tx.UnbondTo) if err != nil { return err } if outTotal > inTotal { return types.ErrTxInsufficientFunds } fee := inTotal - outTotal fees += fee // Good! Adjust accounts adjustByInputs(accounts, tx.Inputs) for _, acc := range accounts { blockCache.UpdateAccount(acc) } // Add ValidatorInfo _s.SetValidatorInfo(&types.ValidatorInfo{ Address: tx.PubKey.Address(), PubKey: tx.PubKey, UnbondTo: tx.UnbondTo, FirstBondHeight: _s.LastBlockHeight + 1, FirstBondAmount: outTotal, }) // Add Validator added := _s.BondedValidators.Add(&types.Validator{ Address: tx.PubKey.Address(), PubKey: tx.PubKey, BondHeight: _s.LastBlockHeight + 1, VotingPower: outTotal, Accum: 0, }) if !added { PanicCrisis("Failed to add validator") } if evc != nil { // TODO: fire for all inputs evc.FireEvent(types.EventStringBond(), types.EventDataTx{tx, nil, ""}) } return nil case *types.UnbondTx: // The validator must be active _, val := _s.BondedValidators.GetByAddress(tx.Address) if val == nil { return types.ErrTxInvalidAddress } // Verify the signature signBytes := acm.SignBytes(_s.ChainID, tx) if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { return types.ErrTxInvalidSignature } // tx.Height must be greater than val.LastCommitHeight if tx.Height <= val.LastCommitHeight { return errors.New("Invalid unbond height") } // Good! _s.unbondValidator(val) if evc != nil { evc.FireEvent(types.EventStringUnbond(), types.EventDataTx{tx, nil, ""}) } return nil case *types.RebondTx: // The validator must be inactive _, val := _s.UnbondingValidators.GetByAddress(tx.Address) if val == nil { return types.ErrTxInvalidAddress } // Verify the signature signBytes := acm.SignBytes(_s.ChainID, tx) if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { return types.ErrTxInvalidSignature } // tx.Height must be in a suitable range minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2) maxRebondHeight := _s.LastBlockHeight + 2 if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) { return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v", minRebondHeight, tx.Height, maxRebondHeight)) } // Good! _s.rebondValidator(val) if evc != nil { evc.FireEvent(types.EventStringRebond(), types.EventDataTx{tx, nil, ""}) } return nil case *types.DupeoutTx: // Verify the signatures _, accused := _s.BondedValidators.GetByAddress(tx.Address) if accused == nil { _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) if accused == nil { return types.ErrTxInvalidAddress } } voteASignBytes := acm.SignBytes(_s.ChainID, &tx.VoteA) voteBSignBytes := acm.SignBytes(_s.ChainID, &tx.VoteB) if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { return types.ErrTxInvalidSignature } // Verify equivocation // TODO: in the future, just require one vote from a previous height that // doesn't exist on this chain. if tx.VoteA.Height != tx.VoteB.Height { return errors.New("DupeoutTx heights don't match") } if tx.VoteA.Round != tx.VoteB.Round { return errors.New("DupeoutTx rounds don't match") } if tx.VoteA.Type != tx.VoteB.Type { return errors.New("DupeoutTx types don't match") } if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { return errors.New("DupeoutTx blockhashes shouldn't match") } // Good! (Bad validator!) _s.destroyValidator(accused) if evc != nil { evc.FireEvent(types.EventStringDupeout(), types.EventDataTx{tx, nil, ""}) } return nil case *types.PermissionsTx: var inAcc *acm.Account // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) if inAcc == nil { log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) return types.ErrTxInvalidAddress } permFlag := tx.PermArgs.PermFlag() // check permission if !HasPermission(blockCache, inAcc, permFlag) { return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag) } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) return err } signBytes := acm.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) return err } value := tx.Input.Amount log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs) var permAcc *acm.Account switch args := tx.PermArgs.(type) { case *ptypes.HasBaseArgs: // this one doesn't make sense from txs return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain") case *ptypes.SetBaseArgs: if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) } err = permAcc.Permissions.Base.Set(args.Permission, args.Value) case *ptypes.UnsetBaseArgs: if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) } err = permAcc.Permissions.Base.Unset(args.Permission) case *ptypes.SetGlobalArgs: if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil { PanicSanity("can't find global permissions account") } err = permAcc.Permissions.Base.Set(args.Permission, args.Value) case *ptypes.HasRoleArgs: return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain") case *ptypes.AddRoleArgs: if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) } if !permAcc.Permissions.AddRole(args.Role) { return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address) } case *ptypes.RmRoleArgs: if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) } if !permAcc.Permissions.RmRole(args.Role) { return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address) } default: PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag))) } // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? if err != nil { return err } // Good! inAcc.Sequence += 1 inAcc.Balance -= value blockCache.UpdateAccount(inAcc) if permAcc != nil { blockCache.UpdateAccount(permAcc) } if evc != nil { evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""}) evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), types.EventDataTx{tx, nil, ""}) } return nil default: // binary decoding should not let this happen PanicSanity("Unknown Tx type") return nil } }
func (privVal *PrivValidator) SignVoteUnsafe(chainID string, vote *Vote) { vote.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, vote)).(acm.SignatureEd25519) }
// This should match the leaf hashes of Block.Data.Hash()'s SimpleMerkleTree. func TxID(chainID string, tx Tx) []byte { signBytes := acm.SignBytes(chainID, tx) return wire.BinaryRipemd160(signBytes) }
func gen_tx() { // Get State, which may be nil. stateDB := dbm.GetDB("state") state := sm.LoadState(stateDB) // Get source pubkey srcPubKeyBytes := getByteSliceFromHex("Enter source pubkey: ") r, n, err := bytes.NewReader(srcPubKeyBytes), new(int64), new(error) srcPubKey := binary.ReadBinary(struct{ account.PubKey }{}, r, n, err).(struct{ account.PubKey }).PubKey if *err != nil { Exit(Fmt("Invalid PubKey. Error: %v", err)) } // Get the state of the account. var srcAccount *account.Account var srcAccountAddress = srcPubKey.Address() var srcAccountBalanceStr = "unknown" var srcAccountSequenceStr = "unknown" srcAddress := srcPubKey.Address() if state != nil { srcAccount = state.GetAccount(srcAddress) srcAccountBalanceStr = Fmt("%v", srcAccount.Balance) srcAccountSequenceStr = Fmt("%v", srcAccount.Sequence+1) } // Get the amount to send from src account srcSendAmount := getUint64(Fmt("Enter amount to send from %X (total: %v): ", srcAccountAddress, srcAccountBalanceStr)) // Get the next sequence of src account srcSendSequence := uint(getUint64(Fmt("Enter next sequence for %X (guess: %v): ", srcAccountAddress, srcAccountSequenceStr))) // Get dest address dstAddress := getByteSliceFromHex("Enter destination address: ") // Get the amount to send to dst account dstSendAmount := getUint64(Fmt("Enter amount to send to %X: ", dstAddress)) // Construct SendTx tx := &types.SendTx{ Inputs: []*types.TxInput{ &types.TxInput{ Address: srcAddress, Amount: srcSendAmount, Sequence: srcSendSequence, Signature: account.SignatureEd25519{}, PubKey: srcPubKey, }, }, Outputs: []*types.TxOutput{ &types.TxOutput{ Address: dstAddress, Amount: dstSendAmount, }, }, } // Show the intermediate form. fmt.Printf("Generated tx: %X\n", binary.BinaryBytes(tx)) // Get source privkey (for signing) srcPrivKeyBytes := getByteSliceFromHex("Enter source privkey (for signing): ") r, n, err = bytes.NewReader(srcPrivKeyBytes), new(int64), new(error) srcPrivKey := binary.ReadBinary(struct{ account.PrivKey }{}, r, n, err).(struct{ account.PrivKey }).PrivKey if *err != nil { Exit(Fmt("Invalid PrivKey. Error: %v", err)) } // Sign tx.Inputs[0].Signature = srcPrivKey.Sign(account.SignBytes(tx)) fmt.Printf("Signed tx: %X\n", binary.BinaryBytes(tx)) }
// executes transactions of a block, does not check block.StateHash // NOTE: If an error occurs during block execution, state will be left // at an invalid state. Copy the state before calling execBlock! func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error { // Basic block validation. err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) if err != nil { return err } // Validate block Validation. if block.Height == 1 { if len(block.Validation.Commits) != 0 { return errors.New("Block at height 1 (first block) should have no Validation commits") } } else { if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() { return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", s.LastBondedValidators.Size(), len(block.Validation.Commits))) } var sumVotingPower uint64 s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { commit := block.Validation.Commits[index] if commit.IsZero() { return false } else { vote := &types.Vote{ Height: block.Height - 1, Round: commit.Round, Type: types.VoteTypeCommit, BlockHash: block.LastBlockHash, BlockParts: block.LastBlockParts, } if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) { sumVotingPower += val.VotingPower return false } else { log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote)) err = errors.New("Invalid validation signature") return true } } }) if err != nil { return err } if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 { return errors.New("Insufficient validation voting power") } } // Update Validator.LastCommitHeight as necessary. for i, commit := range block.Validation.Commits { if commit.IsZero() { continue } _, val := s.LastBondedValidators.GetByIndex(uint(i)) if val == nil { panic(Fmt("Failed to fetch validator at index %v", i)) } if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { val_.LastCommitHeight = block.Height - 1 updated := s.BondedValidators.Update(val_) if !updated { panic("Failed to update bonded validator LastCommitHeight") } } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { val_.LastCommitHeight = block.Height - 1 updated := s.UnbondingValidators.Update(val_) if !updated { panic("Failed to update unbonding validator LastCommitHeight") } } else { panic("Could not find validator") } } // Remember LastBondedValidators s.LastBondedValidators = s.BondedValidators.Copy() // Create BlockCache to cache changes to state. blockCache := NewBlockCache(s) // Commit each tx for _, tx := range block.Data.Txs { err := ExecTx(blockCache, tx, true, s.evc) if err != nil { return InvalidTxError{tx, err} } } // Now sync the BlockCache to the backend. blockCache.Sync() // If any unbonding periods are over, // reward account with bonded coins. toRelease := []*Validator{} s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool { if val.UnbondHeight+unbondingPeriodBlocks < block.Height { toRelease = append(toRelease, val) } return false }) for _, val := range toRelease { s.releaseValidator(val) } // If any validators haven't signed in a while, // unbond them, they have timed out. toTimeout := []*Validator{} s.BondedValidators.Iterate(func(index uint, val *Validator) bool { lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight) if lastActivityHeight+validatorTimeoutBlocks < block.Height { log.Info("Validator timeout", "validator", val, "height", block.Height) toTimeout = append(toTimeout, val) } return false }) for _, val := range toTimeout { s.unbondValidator(val) } // Increment validator AccumPowers s.BondedValidators.IncrementAccum(1) s.LastBlockHeight = block.Height s.LastBlockHash = block.Hash() s.LastBlockParts = blockPartsHeader s.LastBlockTime = block.Time return nil }