diff --git a/accountmanager/accountmanager.go b/accountmanager/accountmanager.go index 331ab986..447d192b 100644 --- a/accountmanager/accountmanager.go +++ b/accountmanager/accountmanager.go @@ -620,6 +620,7 @@ func (am *AccountManager) getParentAccount(accountName common.Name, parentIndex // RecoverTx Make sure the transaction is signed properly and validate account authorization. func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) error { + authorVersion := make(map[common.Name]common.Hash) for _, action := range tx.GetActions() { pubs, err := types.RecoverMultiKey(signer, action, tx) if err != nil { @@ -647,7 +648,6 @@ func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) } } - authorVersion := make(map[common.Name]common.Hash) for name, acctAuthor := range recoverRes.acctAuthors { var count uint64 for _, weight := range acctAuthor.indexWeight { @@ -665,6 +665,53 @@ func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) types.StoreAuthorCache(action, authorVersion) } + if tx.PayerExist() { + for _, action := range tx.GetActions() { + pubs, err := types.RecoverPayerMultiKey(signer, action, tx) + if err != nil { + return err + } + + if uint64(len(pubs)) > params.MaxSignLength { + return fmt.Errorf("exceed max sign length, want most %d, actual is %d", params.MaxSignLength, len(pubs)) + } + + sig := action.PayerSignature() + if sig == nil { + return fmt.Errorf("payer signature is nil") + } + parentIndex := sig.ParentIndex + signSender, err := am.getParentAccount(action.Payer(), parentIndex) + if err != nil { + return err + } + recoverRes := &recoverActionResult{make(map[common.Name]*accountAuthor)} + for i, pub := range pubs { + index := sig.SignData[uint64(i)].Index + if uint64(len(index)) > params.MaxSignDepth { + return fmt.Errorf("exceed max sign depth, want most %d, actual is %d", params.MaxSignDepth, len(index)) + } + + if err := am.ValidSign(signSender, pub, index, recoverRes); err != nil { + return err + } + } + + for name, acctAuthor := range recoverRes.acctAuthors { + var count uint64 + for _, weight := range acctAuthor.indexWeight { + count += weight + } + threshold := acctAuthor.threshold + if count < threshold { + return fmt.Errorf("account %s want threshold %d, but actual is %d", name, threshold, count) + } + authorVersion[name] = acctAuthor.version + } + + types.StoreAuthorCache(action, authorVersion) + } + } return nil } diff --git a/processor/error.go b/processor/error.go index 5788cd44..b862bdbd 100644 --- a/processor/error.go +++ b/processor/error.go @@ -55,7 +55,8 @@ var ( errParentBlock = errors.New("parent block not exist") - ErrActionInvalid = errors.New("action field invalid") + ErrActionInvalid = errors.New("action field invalid") + errPayerNotSupport = errors.New("payer not support") ) // GenesisMismatchError is raised when trying to overwrite an existing diff --git a/processor/processor.go b/processor/processor.go index cced30e0..712c3f12 100644 --- a/processor/processor.go +++ b/processor/processor.go @@ -24,6 +24,7 @@ import ( "github.com/fractalplatform/fractal/accountmanager" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/consensus" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/processor/vm" "github.com/fractalplatform/fractal/state" "github.com/fractalplatform/fractal/types" @@ -100,7 +101,6 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo if assetID != tx.GasAssetID() { return nil, 0, fmt.Errorf("only support system asset %d as tx fee", p.bc.Config().SysTokenID) } - gasPrice := tx.GasPrice() //timer for vm exec overtime var t *time.Timer // @@ -125,11 +125,26 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo return nil, 0, ErrNonceTooLow } + var gasPayer = action.Sender() + var gasPrice = tx.GasPrice() + if tx.PayerExist() { + if header.CurForkID() >= params.ForkID4 { + gasPayer = action.Payer() + gasPrice = action.PayerGasPrice() + } else { + return nil, 0, errPayerNotSupport + } + } else { + if action.PayerIsExist() { + return nil, 0, errPayerNotSupport + } + } + evmcontext := &EvmContext{ ChainContext: p.bc, EngineContext: p.engine, } - context := NewEVMContext(action.Sender(), action.Recipient(), assetID, tx.GasPrice(), header, evmcontext, author) + context := NewEVMContext(action.Sender(), action.Recipient(), assetID, gasPrice, header, evmcontext, author) vmenv := vm.NewEVM(context, accountDB, statedb, config, cfg) //will abort the vm if overtime @@ -139,7 +154,7 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo }) } - _, gas, failed, err, vmerr := ApplyMessage(accountDB, vmenv, action, gp, gasPrice, assetID, config, p.engine) + _, gas, failed, err, vmerr := ApplyMessage(accountDB, vmenv, action, gp, gasPrice, gasPayer, assetID, config, p.engine) if false == cfg.EndTime.IsZero() { //close timer diff --git a/processor/transition.go b/processor/transition.go index 2070ccba..7d89b2f5 100644 --- a/processor/transition.go +++ b/processor/transition.go @@ -44,6 +44,7 @@ type StateTransition struct { gas uint64 initialGas uint64 gasPrice *big.Int + gasPayer common.Name assetID uint64 account *accountmanager.AccountManager evm *vm.EVM @@ -52,7 +53,7 @@ type StateTransition struct { // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, - action *types.Action, gp *common.GasPool, gasPrice *big.Int, assetID uint64, + action *types.Action, gp *common.GasPool, gasPrice *big.Int, gasPayer common.Name, assetID uint64, config *params.ChainConfig, engine EngineContext) *StateTransition { return &StateTransition{ engine: engine, @@ -61,6 +62,7 @@ func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, evm: evm, action: action, gasPrice: gasPrice, + gasPayer: gasPayer, assetID: assetID, account: accountDB, chainConfig: config, @@ -69,9 +71,9 @@ func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, // ApplyMessage computes the new state by applying the given message against the old state within the environment. func ApplyMessage(accountDB *accountmanager.AccountManager, evm *vm.EVM, - action *types.Action, gp *common.GasPool, gasPrice *big.Int, + action *types.Action, gp *common.GasPool, gasPrice *big.Int, gasPayer common.Name, assetID uint64, config *params.ChainConfig, engine EngineContext) ([]byte, uint64, bool, error, error) { - return NewStateTransition(accountDB, evm, action, gp, gasPrice, + return NewStateTransition(accountDB, evm, action, gp, gasPrice, gasPayer, assetID, config, engine).TransitionDb() } @@ -89,7 +91,7 @@ func (st *StateTransition) preCheck() error { func (st *StateTransition) buyGas() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.action.Gas()), st.gasPrice) - balance, err := st.account.GetAccountBalanceByID(st.from, st.assetID, 0) + balance, err := st.account.GetAccountBalanceByID(st.gasPayer, st.assetID, 0) if err != nil { return err } @@ -101,7 +103,7 @@ func (st *StateTransition) buyGas() error { } st.gas += st.action.Gas() st.initialGas = st.action.Gas() - return st.account.TransferAsset(st.from, common.Name(st.chainConfig.FeeName), st.assetID, mgval) + return st.account.TransferAsset(st.gasPayer, common.Name(st.chainConfig.FeeName), st.assetID, mgval) } // TransitionDb will transition the state by applying the current message and @@ -403,7 +405,7 @@ func (st *StateTransition) distributeFee() error { func (st *StateTransition) refundGas() { remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.account.TransferAsset(common.Name(st.chainConfig.FeeName), st.from, st.assetID, remaining) + st.account.TransferAsset(common.Name(st.chainConfig.FeeName), st.gasPayer, st.assetID, remaining) st.gp.AddGas(st.gas) } diff --git a/processor/validator.go b/processor/validator.go index 1110116e..172bded3 100644 --- a/processor/validator.go +++ b/processor/validator.go @@ -133,9 +133,16 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return err } - // Header validity is known at this point, check the uncles and transactions - if hash := types.DeriveTxsMerkleRoot(block.Txs); hash != block.TxHash() { - return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + if block.CurForkID() >= params.ForkID4 { + // Header validity is known at this point, check the uncles and transactions + if hash := types.DeriveExtensTxsMerkleRoot(block.Txs); hash != block.TxHash() { + return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + } + } else { + // Header validity is known at this point, check the uncles and transactions + if hash := types.DeriveTxsMerkleRoot(block.Txs); hash != block.TxHash() { + return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + } } return nil } diff --git a/processor/vm/vm.go b/processor/vm/vm.go index 478fbf1a..601905a7 100644 --- a/processor/vm/vm.go +++ b/processor/vm/vm.go @@ -580,9 +580,17 @@ func (evm *EVM) Create(caller ContractRef, action *types.Action, gas uint64) (re snapshot := evm.StateDB.Snapshot() if b, err := evm.AccountDB.AccountHaveCode(contractName); err != nil { - return nil, 0, err + if evm.ForkID >= params.ForkID4 { + return nil, gas, err + } else { + return nil, 0, err + } } else if b { - return nil, 0, ErrContractCodeCollision + if evm.ForkID >= params.ForkID4 { + return nil, gas, ErrContractCodeCollision + } else { + return nil, 0, ErrContractCodeCollision + } } if err := evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), evm.AssetID, action.Value()); err != nil { diff --git a/rpcapi/blockchain.go b/rpcapi/blockchain.go index f0b00e7b..d97f4de3 100644 --- a/rpcapi/blockchain.go +++ b/rpcapi/blockchain.go @@ -126,6 +126,23 @@ func (s *PublicBlockChainAPI) GetTransactionReceipt(ctx context.Context, hash co return receipt.NewRPCReceipt(blockHash, blockNumber, index, tx), nil } +func (s *PublicBlockChainAPI) GetTransactionReceiptWithPayer(ctx context.Context, hash common.Hash) (*types.RPCReceiptWithPayer, error) { + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash) + if tx == nil { + return nil, nil + } + + receipts, err := s.b.GetReceipts(ctx, blockHash) + if err != nil { + return nil, err + } + if len(receipts) <= int(index) { + return nil, nil + } + receipt := receipts[index] + return receipt.NewRPCReceiptWithPayer(blockHash, blockNumber, index, tx), nil +} + func (s *PublicBlockChainAPI) GetBlockAndResultByNumber(ctx context.Context, blockNr rpc.BlockNumber) *types.BlockAndResult { r := s.b.GetBlockDetailLog(ctx, blockNr) if r == nil { @@ -306,7 +323,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // and apply the message. gp := new(common.GasPool).AddGas(math.MaxUint64) action := types.NewAction(args.ActionType, args.From, args.To, 0, assetID, gas, value, args.Data, args.Remark) - res, gas, failed, err, _ := processor.ApplyMessage(account, evm, action, gp, gasPrice, assetID, s.b.ChainConfig(), s.b.Engine()) + res, gas, failed, err, _ := processor.ApplyMessage(account, evm, action, gp, gasPrice, action.Sender(), assetID, s.b.ChainConfig(), s.b.Engine()) if err := vmError(); err != nil { return nil, 0, false, err } diff --git a/test/multisig/transaction_contract.go b/test/multisig/transaction_contract.go index 97d31fd6..5d094171 100644 --- a/test/multisig/transaction_contract.go +++ b/test/multisig/transaction_contract.go @@ -36,9 +36,10 @@ var ( privateKey, _ = crypto.HexToECDSA("289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032") from = common.Name("fractal.founder") to = common.Name("fractal.account") - aca = common.Name("accounta") - acb = common.Name("accountb") - acc = common.Name("accountc") + aca = common.Name("fcoinaccounta") + acb = common.Name("fcoinaccountb") + acc = common.Name("fcoinaccountc") + acd = common.Name("fcoinaccountd") a_author_0_priv *ecdsa.PrivateKey a_author_2_priv *ecdsa.PrivateKey @@ -48,17 +49,22 @@ var ( c_author_0_priv *ecdsa.PrivateKey c_author_1_priv *ecdsa.PrivateKey c_author_2_priv *ecdsa.PrivateKey + d_author_0_priv *ecdsa.PrivateKey newPrivateKey_a *ecdsa.PrivateKey newPrivateKey_b *ecdsa.PrivateKey newPrivateKey_c *ecdsa.PrivateKey - pubKey_a common.PubKey - pubKey_b common.PubKey - pubKey_c common.PubKey + newPrivateKey_d *ecdsa.PrivateKey + + pubKey_a common.PubKey + pubKey_b common.PubKey + pubKey_c common.PubKey + pubKey_d common.PubKey aNonce = uint64(0) bNonce = uint64(0) cNonce = uint64(0) + dNonce = uint64(0) assetID = uint64(0) nonce = uint64(0) @@ -83,12 +89,18 @@ func generateAccount() { c_author_0_priv = newPrivateKey_c fmt.Println("priv_c ", hex.EncodeToString(crypto.FromECDSA(newPrivateKey_c)), " pubKey_c ", pubKey_c.String()) + newPrivateKey_d, _ = crypto.GenerateKey() + pubKey_d = common.BytesToPubKey(crypto.FromECDSAPub(&newPrivateKey_d.PublicKey)) + d_author_0_priv = newPrivateKey_d + fmt.Println("priv_d ", hex.EncodeToString(crypto.FromECDSA(newPrivateKey_d)), " pubKey_d ", pubKey_d.String()) + balance, _ := testcommon.GetAccountBalanceByID(from, assetID) balance.Div(balance, big.NewInt(10)) - aca = common.Name(fmt.Sprintf("accounta%d", nonce)) - acb = common.Name(fmt.Sprintf("accountb%d", nonce)) - acc = common.Name(fmt.Sprintf("accountc%d", nonce)) + aca = common.Name(fmt.Sprintf("fcoinaccounta%d", nonce)) + acb = common.Name(fmt.Sprintf("fcoinaccountb%d", nonce)) + acc = common.Name(fmt.Sprintf("fcoinaccountc%d", nonce)) + acd = common.Name(fmt.Sprintf("fcoinaccountd%d", nonce)) key := types.MakeKeyPair(privateKey, []uint64{0}) acct := &accountmanager.CreateAccountAction{ @@ -97,7 +109,7 @@ func generateAccount() { PublicKey: pubKey_a, } b, _ := rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce, assetID, balance, b, []*types.KeyPair{key}, nil, nil) acct = &accountmanager.CreateAccountAction{ AccountName: acb, @@ -105,7 +117,7 @@ func generateAccount() { PublicKey: pubKey_b, } b, _ = rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce+1, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce+1, assetID, balance, b, []*types.KeyPair{key}, nil, nil) acct = &accountmanager.CreateAccountAction{ AccountName: acc, @@ -113,27 +125,39 @@ func generateAccount() { PublicKey: pubKey_c, } b, _ = rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce+2, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce+2, assetID, balance, b, []*types.KeyPair{key}, nil, nil) + + acct = &accountmanager.CreateAccountAction{ + AccountName: acd, + Founder: acd, + PublicKey: pubKey_d, + } + b, _ = rlp.EncodeToBytes(acct) + sendTransferTx(types.CreateAccount, from, to, nonce+3, assetID, balance, b, []*types.KeyPair{key}, nil, nil) for { time.Sleep(10 * time.Second) aexist, _ := testcommon.CheckAccountIsExist(aca) bexist, _ := testcommon.CheckAccountIsExist(acb) cexist, _ := testcommon.CheckAccountIsExist(acc) + dexist, _ := testcommon.CheckAccountIsExist(acd) acaAccount, _ := testcommon.GetAccountByName(aca) acbAccount, _ := testcommon.GetAccountByName(acb) accAccount, _ := testcommon.GetAccountByName(acc) + acdAccount, _ := testcommon.GetAccountByName(acd) + fmt.Println("acaAccount version hash", acaAccount.AuthorVersion.Hex()) fmt.Println("acbAccount version hash", acbAccount.AuthorVersion.Hex()) fmt.Println("accAccount version hash", accAccount.AuthorVersion.Hex()) + fmt.Println("accAccount version hash", acdAccount.AuthorVersion.Hex()) - if aexist && bexist && cexist { + if aexist && bexist && cexist && dexist { break } } - fmt.Println("aca ", aca, " acb ", acb, " acc ", acc) + fmt.Println("aca ", aca, " acb ", acb, " acc ", acc, " acd ", acd) } func init() { @@ -170,7 +194,8 @@ func addAuthorsForAca() { return } key := types.MakeKeyPair(newPrivateKey_a, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + + sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func addAuthorsForAcb() { @@ -192,7 +217,7 @@ func addAuthorsForAcb() { return } key := types.MakeKeyPair(newPrivateKey_b, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, acb, to, bNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + sendTransferTx(types.UpdateAccountAuthor, acb, to, bNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func addAuthorsForAcc() { @@ -216,7 +241,7 @@ func addAuthorsForAcc() { return } key := types.MakeKeyPair(newPrivateKey_c, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, acc, to, cNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + sendTransferTx(types.UpdateAccountAuthor, acc, to, cNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func transferFromA2B() { @@ -229,7 +254,7 @@ func transferFromA2B() { key_1_2 := types.MakeKeyPair(b_author_2_priv, []uint64{1, 2}) aNonce++ - sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}) + sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}, nil, nil) } func modifyAUpdateAUthorThreshold() { @@ -250,7 +275,28 @@ func modifyAUpdateAUthorThreshold() { } aNonce++ - sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2, key_0}) + sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2, key_0}, nil, nil) +} + +func transferFromA2BWithBAsPayer() { + key_1_0 := types.MakeKeyPair(b_author_0_priv, []uint64{1, 0}) + key_1_1_0 := types.MakeKeyPair(c_author_0_priv, []uint64{1, 1, 0}) + key_1_1_1 := types.MakeKeyPair(c_author_1_priv, []uint64{1, 1, 1}) + key_1_1_2 := types.MakeKeyPair(c_author_2_priv, []uint64{1, 1, 2}) + key_2 := types.MakeKeyPair(a_author_2_priv, []uint64{2}) + key_3 := types.MakeKeyPair(a_author_3_priv, []uint64{3}) + key_1_2 := types.MakeKeyPair(b_author_2_priv, []uint64{1, 2}) + + gasPrice, _ := testcommon.GasPrice() + fp := &types.FeePayer{ + GasPrice: gasPrice, + Payer: acd, + Sign: &types.Signature{0, make([]*types.SignData, 0)}, + } + payerKey := types.MakeKeyPair(newPrivateKey_d, []uint64{0}) + + aNonce++ + sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}, fp, []*types.KeyPair{payerKey}) } func main() { @@ -276,11 +322,16 @@ func main() { transferFromA2B() modifyAUpdateAUthorThreshold() + + transferFromA2BWithBAsPayer() } -func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetID uint64, value *big.Int, input []byte, keys []*types.KeyPair) { +func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetID uint64, value *big.Int, input []byte, keys []*types.KeyPair, fp *types.FeePayer, payerKeys []*types.KeyPair) { action := types.NewAction(txType, from, to, nonce, assetID, gasLimit, value, input, nil) gasprice, _ := testcommon.GasPrice() + if fp != nil { + gasprice = big.NewInt(0) + } tx := types.NewTransaction(0, gasprice, action) signer := types.MakeSigner(big.NewInt(1)) @@ -289,6 +340,13 @@ func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetI jww.ERROR.Fatalln(err) } + if fp != nil { + err = types.SignPayerActionWithMultiKey(action, tx, signer, fp, 0, payerKeys) + if err != nil { + jww.ERROR.Fatalln(err) + } + } + rawtx, err := rlp.EncodeToBytes(tx) if err != nil { jww.ERROR.Fatalln(err) diff --git a/txpool/error.go b/txpool/error.go index abb05b26..9d34c5d8 100644 --- a/txpool/error.go +++ b/txpool/error.go @@ -24,10 +24,10 @@ var ( // ErrInvalidSender is returned if the transaction contains an invalid signature. ErrInvalidSender = errors.New("invalid sender") + ErrPayerTx = errors.New("payer is exist") // ErrNonceTooLow is returned if the nonce of a transaction is lower than the // one present in the local chain. ErrNonceTooLow = errors.New("nonce too low") - // ErrUnderpriced is returned if a transaction's gas price is below the minimum // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") diff --git a/txpool/test_utils.go b/txpool/test_utils.go index ae14c536..940fd2c3 100644 --- a/txpool/test_utils.go +++ b/txpool/test_utils.go @@ -84,6 +84,32 @@ func transaction(nonce uint64, from, to common.Name, gaslimit uint64, key *ecdsa return pricedTransaction(nonce, from, to, gaslimit, big.NewInt(1), key) } +func extendTransaction(nonce uint64, payer, from, to common.Name, gasLimit uint64, key, payerKey *ecdsa.PrivateKey) *types.Transaction { + + fp := &types.FeePayer{ + GasPrice: big.NewInt(100), + Payer: payer, + Sign: &types.Signature{0, make([]*types.SignData, 0)}, + } + + action := types.NewAction(types.Transfer, from, to, nonce, 0, gasLimit, big.NewInt(100), nil, nil) + tx := types.NewTransaction(0, big.NewInt(0), action) + signer := types.MakeSigner(params.DefaultChainconfig.ChainID) + keyPair := types.MakeKeyPair(key, []uint64{0}) + err := types.SignActionWithMultiKey(action, tx, signer, 0, []*types.KeyPair{keyPair}) + if err != nil { + panic(err) + } + + payerKeyPair := types.MakeKeyPair(payerKey, []uint64{0}) + err = types.SignPayerActionWithMultiKey(action, tx, signer, fp, 0, []*types.KeyPair{payerKeyPair}) + if err != nil { + panic(err) + } + + return tx +} + func pricedTransaction(nonce uint64, from, to common.Name, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { tx := newTx(gasprice, newAction(nonce, from, to, big.NewInt(100), gaslimit, nil)) keyPair := types.MakeKeyPair(key, []uint64{0}) diff --git a/txpool/txpool.go b/txpool/txpool.go index df9173d7..e3ceb9c5 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -651,6 +651,7 @@ func (tp *TxPool) local() map[common.Name][]*types.Transaction { func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { validateAction := func(tx *types.Transaction, action *types.Action) error { from := action.Sender() + // Drop non-local transactions under our own minimal accepted gas price local = local || tp.locals.contains(from) // account may be local even if the transaction arrived from the network if !local && tp.gasPrice.Cmp(tx.GasPrice()) > 0 { @@ -666,10 +667,27 @@ func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrNonceTooLow } - // Transactor should have enough funds to cover the gas costs - balance, err := tp.curAccountManager.GetAccountBalanceByID(from, tx.GasAssetID(), 0) - if err != nil { - return err + // wait fork successed, remove it + if action.PayerIsExist() && tp.chain.CurrentBlock().CurForkID() < params.ForkID4 { + return fmt.Errorf("This type of transaction: %v is not currently supported", tx.Hash().Hex()) + } + + var balance *big.Int + if tx.PayerExist() { + // Transactor should have enough funds to cover the gas costs + balance, err = tp.curAccountManager.GetAccountBalanceByID(action.Payer(), tx.GasAssetID(), 0) + if err != nil { + return err + } + } else { + if action.PayerIsExist() { + return ErrPayerTx + } + // Transactor should have enough funds to cover the gas costs + balance, err = tp.curAccountManager.GetAccountBalanceByID(from, tx.GasAssetID(), 0) + if err != nil { + return err + } } gascost := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(action.Gas())) @@ -685,7 +703,9 @@ func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { value := action.Value() if tp.config.GasAssetID == action.AssetID() { - value.Add(value, gascost) + if !tx.PayerExist() { + value.Add(value, gascost) + } } if balance.Cmp(value) < 0 { @@ -751,7 +771,6 @@ func (tp *TxPool) add(tx *types.Transaction, local bool) (bool, error) { } // If the transaction is replacing an already pending one, do directly - // todo Change action from := tx.GetActions()[0].Sender() if list := tp.pending[from]; list != nil && list.Overlaps(tx) { // Nonce already pending, check if required price bump is met @@ -764,6 +783,7 @@ func (tp *TxPool) add(tx *types.Transaction, local bool) (bool, error) { tp.all.Remove(old.Hash()) tp.priced.Removed(1) } + tp.all.Add(tx) tp.priced.Put(tx) tp.journalTx(from, tx) @@ -913,7 +933,7 @@ func (tp *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { // Cache senders in transactions before obtaining lock (pool.signer is immutable) for index, tx := range txs { // If the transaction is already known, discard it - if tp.all.Get(tx.Hash()) != nil { + if storgeTx := tp.all.Get(tx.Hash()); storgeTx != nil && tx.GasPrice().Cmp(storgeTx.GasPrice()) == 0 { log.Trace("Discarding already known transaction", "hash", tx.Hash()) errs[index] = errors.New("already known transaction") continue diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index d471fb38..69c554c5 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -44,6 +44,67 @@ func TestConfigCheck(t *testing.T) { assert.Equal(t, cfg.check(), *DefaultTxPoolConfig) } +// func TestAddPayerTx(t *testing.T) { +// var ( +// statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) +// manager, _ = am.NewAccountManager(statedb) +// fname = common.Name("fromname") +// tname = common.Name("totestname") +// fkey = generateAccount(t, fname, manager) +// tkey = generateAccount(t, tname, manager) +// asset = asset.NewAsset(statedb) +// ) + +// // issue asset +// if _, err := asset.IssueAsset("ft", 0, 0, "zz", new(big.Int).SetUint64(params.Fractal), 10, common.Name(""), fname, new(big.Int).SetUint64(params.Fractal), common.Name(""), ""); err != nil { +// t.Fatal(err) +// } + +// // add balance +// if err := manager.AddAccountBalanceByName(fname, "ft", new(big.Int).SetUint64(params.Fractal)); err != nil { +// t.Fatal(err) +// } + +// if err := manager.AddAccountBalanceByName(tname, "ft", new(big.Int).SetUint64(params.Fractal)); err != nil { +// t.Fatal(err) +// } + +// blockchain := &testBlockChain{statedb, 1000000000, new(event.Feed)} +// tx0 := pricedTransaction(0, fname, tname, 109000, big.NewInt(0), fkey) +// tx1 := extendTransaction(0, tname, fname, tname, 109000, fkey, tkey) + +// params.DefaultChainconfig.SysTokenID = 0 + +// pool := New(testTxPoolConfig, params.DefaultChainconfig, blockchain) +// defer pool.Stop() + +// nonce, err := pool.State().GetNonce(fname) +// if err != nil { +// t.Fatal("Invalid getNonce ", err) +// } +// if nonce != 0 { +// t.Fatalf("Invalid nonce, want 0, got %d", nonce) +// } + +// errs := pool.addRemotesSync([]*types.Transaction{tx0, tx1}) + +// t.Log(errs) +// nonce, err = pool.State().GetNonce(fname) +// if err != nil { +// t.Fatal("Invalid getNonce ", err) +// } + +// if nonce != 1 { +// t.Fatalf("Invalid nonce, want 1, got %d", nonce) +// } + +// result := pool.Get(tx1.Hash()) + +// if !result.PayerExist() { +// t.Fatal("add payer tx failed") +// } +// } + // This test simulates a scenario where a new block is imported during a // state reset and tests whether the pending state is in sync with the // block head event that initiated the resetState(). diff --git a/txpool/utils.go b/txpool/utils.go index 0353d24c..fc14bdd6 100644 --- a/txpool/utils.go +++ b/txpool/utils.go @@ -101,6 +101,9 @@ func IntrinsicGas(accountDB *accountmanager.AccountManager, action *types.Action gas += (uint64(len(action.GetSign()) - 1)) * gasTable.SignGas } + payerSignLen := len(action.GetFeePayerSign()) + gas += uint64(payerSignLen) * gasTable.SignGas + if action.Value().Sign() != 0 { gas += receiptGasFunc(action) } diff --git a/types/action.go b/types/action.go index 01db6d75..22e69622 100644 --- a/types/action.go +++ b/types/action.go @@ -111,6 +111,12 @@ type SignData struct { Index []uint64 } +type FeePayer struct { + GasPrice *big.Int + Payer common.Name + Sign *Signature +} + type actionData struct { AType ActionType Nonce uint64 @@ -121,16 +127,20 @@ type actionData struct { Amount *big.Int Payload []byte Remark []byte + Sign *Signature - Sign *Signature + Extend []rlp.RawValue `rlp:"tail"` } // Action represents an entire action in the transaction. type Action struct { data actionData // cache + fp *FeePayer hash atomic.Value + extendHash atomic.Value senderPubkeys atomic.Value + payerPubkeys atomic.Value author atomic.Value } @@ -150,6 +160,7 @@ func NewAction(actionType ActionType, from, to common.Name, nonce, assetID, gasL Payload: payload, Remark: remark, Sign: &Signature{0, make([]*SignData, 0)}, + Extend: make([]rlp.RawValue, 0), } if amount != nil { data.Amount.Set(amount) @@ -169,6 +180,38 @@ func (a *Action) GetSignParent() uint64 { return a.data.Sign.ParentIndex } +func (a *Action) PayerIsExist() bool { + return a.fp != nil +} + +func (a *Action) PayerGasPrice() *big.Int { + if a.fp != nil { + return a.fp.GasPrice + } + return nil +} + +func (a *Action) Payer() common.Name { + if a.fp != nil { + return a.fp.Payer + } + return common.Name("") +} + +func (a *Action) PayerSignature() *Signature { + if a.fp != nil { + return a.fp.Sign + } + return nil +} + +func (a *Action) GetFeePayerSign() []*SignData { + if a.fp != nil { + return a.fp.Sign.SignData + } + return nil +} + // Check the validity of all fields func (a *Action) Check(fid uint64, conf *params.ChainConfig) error { //check To @@ -287,14 +330,50 @@ func (a *Action) Gas() uint64 { return a.data.GasLimit } // Value returns action's Value. func (a *Action) Value() *big.Int { return new(big.Int).Set(a.data.Amount) } +func (a *Action) Extend() []rlp.RawValue { return a.data.Extend } + +// IgnoreExtend returns ignore extend +func (a *Action) IgnoreExtend() []interface{} { + return []interface{}{ + a.data.AType, + a.data.Nonce, + a.data.AssetID, + a.data.From, + a.data.To, + a.data.GasLimit, + a.data.Amount, + a.data.Payload, + a.data.Remark, + a.data.Sign, + } +} + // EncodeRLP implements rlp.Encoder func (a *Action) EncodeRLP(w io.Writer) error { + if a.fp != nil { + value, err := rlp.EncodeToBytes(a.fp) + if err != nil { + return err + } + a.data.Extend = []rlp.RawValue{value} + } + return rlp.Encode(w, &a.data) } // DecodeRLP implements rlp.Decoder func (a *Action) DecodeRLP(s *rlp.Stream) error { - return s.Decode(&a.data) + if err := s.Decode(&a.data); err != nil { + return err + } + + if len(a.data.Extend) != 0 { + a.fp = new(FeePayer) + return rlp.DecodeBytes(a.data.Extend[0], a.fp) + + } + + return nil } // ChainID returns which chain id this action was signed for (if at all) @@ -307,11 +386,21 @@ func (a *Action) Hash() common.Hash { if hash := a.hash.Load(); hash != nil { return hash.(common.Hash) } - v := RlpHash(a) + v := RlpHash(a.IgnoreExtend()) a.hash.Store(v) return v } +// ExtendHash hashes the RLP encoding of action. +func (a *Action) ExtendHash() common.Hash { + if hash := a.extendHash.Load(); hash != nil { + return hash.(common.Hash) + } + v := RlpHash(a) + a.extendHash.Store(v) + return v +} + // WithSignature returns a new transaction with the given signature. func (a *Action) WithSignature(signer Signer, sig []byte, index []uint64) error { r, s, v, err := signer.SignatureValues(sig) @@ -322,11 +411,32 @@ func (a *Action) WithSignature(signer Signer, sig []byte, index []uint64) error return nil } -// WithSignature returns a new transaction with the given signature. +// WithParentIndex returns a new transaction with the given signature. func (a *Action) WithParentIndex(parentIndex uint64) { a.data.Sign.ParentIndex = parentIndex } +func (f *FeePayer) WithSignature(signer Signer, sig []byte, index []uint64) error { + r, s, v, err := signer.SignatureValues(sig) + if err != nil { + return err + } + f.Sign.SignData = append(f.Sign.SignData, &SignData{R: r, S: s, V: v, Index: index}) + return nil +} + +func (f *FeePayer) WithParentIndex(parentIndex uint64) { + f.Sign.ParentIndex = parentIndex +} + +func (f *FeePayer) GetSignParent() uint64 { + return f.Sign.ParentIndex +} + +func (f *FeePayer) GetSignIndex(i uint64) []uint64 { + return f.Sign.SignData[i].Index +} + // RPCAction represents a action that will serialize to the RPC representation of a action. type RPCAction struct { Type uint64 `json:"type"` diff --git a/types/action_test.go b/types/action_test.go index cc149cce..a754486f 100644 --- a/types/action_test.go +++ b/types/action_test.go @@ -99,8 +99,8 @@ func TestActionEncodeAndDecode(t *testing.T) { if err := rlp.Decode(bytes.NewReader(actionBytes), &actAction); err != nil { t.Fatal(err) } - assert.Equal(t, testAction, actAction) + } func TestAction_Check(t *testing.T) { diff --git a/types/block.go b/types/block.go index 61af0727..3e69bf2f 100644 --- a/types/block.go +++ b/types/block.go @@ -26,6 +26,7 @@ import ( "sync/atomic" "github.com/fractalplatform/fractal/common" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/utils/rlp" ) @@ -93,17 +94,22 @@ func NewBlock(header *Header, txs []*Transaction, receipts []*Receipt) *Block { } b := &Block{Head: header} - b.Head.TxsRoot = DeriveTxsMerkleRoot(txs) + + if header.ForkID.Cur >= params.ForkID4 { + b.Head.TxsRoot = DeriveExtensTxsMerkleRoot(txs) + } else { + b.Head.TxsRoot = DeriveTxsMerkleRoot(txs) + } + b.Head.ReceiptsRoot = DeriveReceiptsMerkleRoot(receipts) b.Txs = make([]*Transaction, len(txs)) copy(b.Txs, txs) b.Head.Bloom = CreateBloom(receipts) - return b } -// NewBlockWithHeader creates a block with the given header data. The +// NewBlockWithHeader create s a block with the given header data. The // header data is copied, changes to header and to the field values // will not affect the block. func NewBlockWithHeader(header *Header) *Block { @@ -288,6 +294,15 @@ func (bs blockSorter) Less(i, j int) bool { return bs.by(bs.blocks[i], bs.blocks // Number represents block sort by number. func Number(b1, b2 *Block) bool { return b1.Head.Number.Cmp(b2.Head.Number) < 0 } +// DeriveExtensTxsMerkleRoot returns Extens txs merkle tree root hash. +func DeriveExtensTxsMerkleRoot(txs []*Transaction) common.Hash { + var txHashs []common.Hash + for i := 0; i < len(txs); i++ { + txHashs = append(txHashs, txs[i].ExtensHash()) + } + return common.MerkleRoot(txHashs) +} + // DeriveTxsMerkleRoot returns txs merkle tree root hash. func DeriveTxsMerkleRoot(txs []*Transaction) common.Hash { var txHashs []common.Hash diff --git a/types/receipt.go b/types/receipt.go index fc0b0865..6f6a7cac 100644 --- a/types/receipt.go +++ b/types/receipt.go @@ -17,6 +17,8 @@ package types import ( + "math/big" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/utils/rlp" @@ -67,6 +69,36 @@ func (a *ActionResult) NewRPCActionResult(aType ActionType) *RPCActionResult { } } +type RPCActionResultWithPayer struct { + ActionType uint64 `json:"actionType"` + Status uint64 `json:"status"` + Index uint64 `json:"index"` + GasUsed uint64 `json:"gasUsed"` + GasAllot []*GasDistribution `json:"gasAllot"` + Error string `json:"error"` + Payer common.Name `json:"payer"` + PayerGasPrice *big.Int `json:"payerGasPrice"` +} + +// NewRPCActionResult returns a ActionResult that will serialize to the RPC. +func (a *ActionResult) NewRPCActionResultWithPayer(action *Action, gasPrice *big.Int) *RPCActionResultWithPayer { + var payer = action.Sender() + var price = gasPrice + if action.fp != nil { + payer = action.fp.Payer + } + return &RPCActionResultWithPayer{ + ActionType: uint64(action.Type()), + Status: a.Status, + Index: a.Index, + GasUsed: a.GasUsed, + GasAllot: a.GasAllot, + Error: a.Error, + Payer: payer, + PayerGasPrice: price, + } +} + // Receipt represents the results of a transaction. type Receipt struct { PostState []byte @@ -137,6 +169,48 @@ func (r *Receipt) NewRPCReceipt(blockHash common.Hash, blockNumber uint64, index return result } +// RPCReceipt that will serialize to the RPC representation of a Receipt. +type RPCReceiptWithPayer struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Hash common.Hash `json:"txHash"` + TransactionIndex uint64 `json:"transactionIndex"` + PostState hexutil.Bytes `json:"postState"` + ActionResults []*RPCActionResultWithPayer `json:"actionResults"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` + TotalGasUsed uint64 `json:"totalGasUsed"` + Bloom Bloom `json:"logsBloom"` + Logs []*RPCLog `json:"logs"` +} + +// NewRPCReceipt returns a Receipt that will serialize to the RPC. +func (r *Receipt) NewRPCReceiptWithPayer(blockHash common.Hash, blockNumber uint64, index uint64, tx *Transaction) *RPCReceiptWithPayer { + result := &RPCReceiptWithPayer{ + BlockHash: blockHash, + BlockNumber: blockNumber, + Hash: tx.Hash(), + TransactionIndex: index, + PostState: hexutil.Bytes(r.PostState), + CumulativeGasUsed: r.CumulativeGasUsed, + TotalGasUsed: r.TotalGasUsed, + Bloom: r.Bloom, + } + + var rpcActionResults []*RPCActionResultWithPayer + for i, a := range tx.GetActions() { + rpcActionResults = append(rpcActionResults, r.ActionResults[i].NewRPCActionResultWithPayer(a, tx.GasPrice())) + } + result.ActionResults = rpcActionResults + + var rlogs []*RPCLog + for _, l := range r.Logs { + rlogs = append(rlogs, l.NewRPCLog()) + } + result.Logs = rlogs + + return result +} + // ConsensusReceipt returns consensus encoding of a receipt. func (r *Receipt) ConsensusReceipt() *Receipt { result := &Receipt{ diff --git a/types/signer.go b/types/signer.go index c06550b2..616907d6 100644 --- a/types/signer.go +++ b/types/signer.go @@ -24,6 +24,7 @@ import ( "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/crypto" + "github.com/fractalplatform/fractal/utils/rlp" ) var ( @@ -88,6 +89,47 @@ func RecoverMultiKey(signer Signer, a *Action, tx *Transaction) ([]common.PubKey return pubKeys, nil } +func SignPayerActionWithMultiKey(a *Action, tx *Transaction, s Signer, feePayer *FeePayer, parentIndex uint64, keys []*KeyPair) error { + a.fp = feePayer + h := s.FeePayerHash(tx) + for _, key := range keys { + sig, err := crypto.Sign(h[:], key.priv) + if err != nil { + return err + } + + err = feePayer.WithSignature(s, sig, key.index) + if err != nil { + return err + } + } + feePayer.WithParentIndex(parentIndex) + + if value, err := rlp.EncodeToBytes(feePayer); err != nil { + return err + } else { + a.data.Extend = append(a.data.Extend, value) + } + + return nil +} + +func RecoverPayerMultiKey(signer Signer, a *Action, tx *Transaction) ([]common.PubKey, error) { + if sc := a.payerPubkeys.Load(); sc != nil { + sigCache := sc.(sigCache) + if sigCache.signer.Equal(signer) { + return sigCache.pubKeys, nil + } + } + + pubKeys, err := signer.PayerPubKeys(a, tx) + if err != nil { + return []common.PubKey{}, err + } + a.payerPubkeys.Store(sigCache{signer: signer, pubKeys: pubKeys}) + return pubKeys, nil +} + func StoreAuthorCache(a *Action, authorVersion map[common.Name]common.Hash) { a.author.Store(authorVersion) } @@ -147,6 +189,25 @@ func (s Signer) PubKeys(a *Action, tx *Transaction) ([]common.PubKey, error) { return pubKeys, nil } +func (s Signer) PayerPubKeys(a *Action, tx *Transaction) ([]common.PubKey, error) { + if len(a.fp.Sign.SignData) == 0 { + return nil, ErrSignEmpty + } + + var pubKeys []common.PubKey + for _, sign := range a.fp.Sign.SignData { + V := new(big.Int).Sub(sign.V, s.chainIDMul) + V.Sub(V, big8) + data, err := recoverPlain(s.FeePayerHash(tx), sign.R, sign.S, V) + if err != nil { + return nil, err + } + pubKey := common.BytesToPubKey(data) + pubKeys = append(pubKeys, pubKey) + } + return pubKeys, nil +} + // SignatureValues returns a new transaction with the given signature. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (s Signer) SignatureValues(sig []byte) (R, S, V *big.Int, err error) { @@ -190,6 +251,33 @@ func (s Signer) Hash(tx *Transaction) common.Hash { }) } +func (s Signer) FeePayerHash(tx *Transaction) common.Hash { + actionHashs := make([]common.Hash, len(tx.GetActions())) + for i, a := range tx.GetActions() { + hash := RlpHash([]interface{}{ + a.data.From, + a.data.AType, + a.data.Nonce, + a.data.To, + a.data.GasLimit, + a.data.Amount, + a.data.Payload, + a.data.AssetID, + a.data.Remark, + a.fp.Payer, + a.fp.GasPrice, + s.chainID, uint(0), uint(0), + }) + actionHashs[i] = hash + } + + return RlpHash([]interface{}{ + common.MerkleRoot(actionHashs), + tx.gasAssetID, + tx.gasPrice, + }) +} + func recoverPlain(sighash common.Hash, R, S, Vb *big.Int) ([]byte, error) { if Vb.BitLen() > 8 { return nil, ErrInvalidSig diff --git a/types/signer_test.go b/types/signer_test.go index 1c487e94..824f002f 100644 --- a/types/signer_test.go +++ b/types/signer_test.go @@ -62,6 +62,49 @@ func TestSigningMultiKey(t *testing.T) { } } +func TestSigningPayerMultiKey(t *testing.T) { + keys := make([]*KeyPair, 0) + pubs := make([]common.PubKey, 0) + for i := 0; i < 4; i++ { + key, _ := crypto.GenerateKey() + exp := crypto.FromECDSAPub(&key.PublicKey) + keys = append(keys, &KeyPair{priv: key, index: []uint64{uint64(i)}}) + pubs = append(pubs, common.BytesToPubKey(exp)) + } + signer := NewSigner(big.NewInt(1)) + fp := &FeePayer{ + GasPrice: big.NewInt(0), + Payer: testTx.GetActions()[0].Recipient(), + Sign: &Signature{0, make([]*SignData, 0)}, + } + if err := SignPayerActionWithMultiKey(testTx.GetActions()[0], testTx, signer, fp, 0, keys); err != nil { + t.Fatal(err) + } + + pubkeys, err := RecoverPayerMultiKey(signer, testTx.GetActions()[0], testTx) + if err != nil { + t.Fatal(err) + } + + for i, pubkey := range pubkeys { + if pubkey.Compare(pubs[i]) != 0 { + t.Errorf("exected from and pubkey to be equal. Got %x want %x", pubkey, pubs[i]) + } + } + + //test cache + pubkeys, err = RecoverPayerMultiKey(signer, testTx.GetActions()[0], testTx) + if err != nil { + t.Fatal(err) + } + + for i, pubkey := range pubkeys { + if pubkey.Compare(pubs[i]) != 0 { + t.Errorf("exected from and pubkey to be equal. Got %x want %x", pubkey, pubs[i]) + } + } +} + func TestChainID(t *testing.T) { key, _ := crypto.GenerateKey() diff --git a/types/transaction.go b/types/transaction.go index 2016aa82..8d6e8dcb 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -45,8 +45,9 @@ type Transaction struct { gasAssetID uint64 gasPrice *big.Int // caches - hash atomic.Value - size atomic.Value + hash atomic.Value + extendHash atomic.Value + size atomic.Value } // NewTransaction initialize a transaction. @@ -65,8 +66,21 @@ func NewTransaction(assetID uint64, price *big.Int, actions ...*Action) *Transac // GasAssetID returns transaction gas asset id. func (tx *Transaction) GasAssetID() uint64 { return tx.gasAssetID } -// GasPrice returns transaction gas price. -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.gasPrice) } +func (tx *Transaction) PayerExist() bool { + return tx.gasPrice.Cmp(big.NewInt(0)) == 0 && tx.actions[0].fp != nil +} + +// GasPrice returns transaction Higher gas price . +func (tx *Transaction) GasPrice() *big.Int { + gasPrice := new(big.Int) + if tx.gasPrice.Cmp(big.NewInt(0)) == 0 { + if price := tx.actions[0].PayerGasPrice(); price == nil { + return big.NewInt(0) + } + return gasPrice.Set(tx.actions[0].PayerGasPrice()) + } + return gasPrice.Set(tx.gasPrice) +} // Cost returns all actions gasprice * gaslimit. func (tx *Transaction) Cost() *big.Int { @@ -111,11 +125,26 @@ func (tx *Transaction) Hash() common.Hash { if hash := tx.hash.Load(); hash != nil { return hash.(common.Hash) } - v := RlpHash(tx) + var acts [][]interface{} + for _, a := range tx.actions { + acts = append(acts, a.IgnoreExtend()) + } + v := RlpHash([]interface{}{tx.gasAssetID, tx.gasPrice, acts}) tx.hash.Store(v) return v } +// ExtensHash hashes the RLP encoding of tx. +func (tx *Transaction) ExtensHash() common.Hash { + if hash := tx.extendHash.Load(); hash != nil { + return hash.(common.Hash) + } + + v := RlpHash(tx) + tx.extendHash.Store(v) + return v +} + // Size returns the true RLP encoded storage size of the transaction, func (tx *Transaction) Size() common.StorageSize { if size := tx.size.Load(); size != nil {