2020-09-25 14:39:11 +00:00
|
|
|
package oracle
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
gio "io"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2020-10-09 07:44:31 +00:00
|
|
|
func (o *Oracle) getResponse(reqID uint64, create bool) *incompleteTx {
|
2020-09-25 14:39:11 +00:00
|
|
|
o.respMtx.Lock()
|
|
|
|
defer o.respMtx.Unlock()
|
|
|
|
incTx, ok := o.responses[reqID]
|
2020-10-09 07:44:31 +00:00
|
|
|
if !ok && create && !o.removed[reqID] {
|
2020-09-25 14:39:11 +00:00
|
|
|
incTx = newIncompleteTx()
|
|
|
|
o.responses[reqID] = incTx
|
|
|
|
}
|
|
|
|
return incTx
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddResponse processes oracle response from node pub.
|
|
|
|
// sig is response transaction signature.
|
|
|
|
func (o *Oracle) AddResponse(pub *keys.PublicKey, reqID uint64, txSig []byte) {
|
2020-10-09 07:44:31 +00:00
|
|
|
incTx := o.getResponse(reqID, true)
|
|
|
|
if incTx == nil {
|
|
|
|
return
|
|
|
|
}
|
2020-09-25 14:39:11 +00:00
|
|
|
|
|
|
|
incTx.Lock()
|
|
|
|
isBackup := false
|
|
|
|
if incTx.tx != nil {
|
|
|
|
ok := pub.Verify(txSig, incTx.tx.GetSignedHash().BytesBE())
|
|
|
|
if !ok {
|
|
|
|
ok = pub.Verify(txSig, incTx.backupTx.GetSignedHash().BytesBE())
|
|
|
|
if !ok {
|
|
|
|
o.Log.Debug("invalid response signature",
|
|
|
|
zap.String("pub", hex.EncodeToString(pub.Bytes())))
|
|
|
|
incTx.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
isBackup = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
incTx.addResponse(pub, txSig, isBackup)
|
2020-10-09 07:44:31 +00:00
|
|
|
readyTx, ready := incTx.finalize(o.getOracleNodes(), false)
|
2020-09-28 11:58:04 +00:00
|
|
|
if ready {
|
|
|
|
ready = !incTx.isSent
|
|
|
|
incTx.isSent = true
|
|
|
|
}
|
2020-09-25 14:39:11 +00:00
|
|
|
incTx.Unlock()
|
|
|
|
|
|
|
|
if ready {
|
2020-09-28 11:58:04 +00:00
|
|
|
o.getOnTransaction()(readyTx)
|
2020-09-25 14:39:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrResponseTooLarge is returned when response exceeds max allowed size.
|
|
|
|
var ErrResponseTooLarge = errors.New("too big response")
|
|
|
|
|
|
|
|
func readResponse(rc gio.ReadCloser, limit int) ([]byte, error) {
|
|
|
|
defer rc.Close()
|
|
|
|
|
|
|
|
buf := make([]byte, limit+1)
|
|
|
|
n, err := gio.ReadFull(rc, buf)
|
|
|
|
if err == gio.ErrUnexpectedEOF && n <= limit {
|
|
|
|
return buf[:n], nil
|
|
|
|
}
|
|
|
|
if err == nil || n > limit {
|
|
|
|
return nil, ErrResponseTooLarge
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateResponseTx creates unsigned oracle response transaction.
|
|
|
|
func (o *Oracle) CreateResponseTx(gasForResponse int64, height uint32, resp *transaction.OracleResponse) (*transaction.Transaction, error) {
|
2021-02-15 14:06:00 +00:00
|
|
|
tx := transaction.New(o.Network, o.oracleResponse, 0)
|
2020-09-25 14:39:11 +00:00
|
|
|
tx.Nonce = uint32(resp.ID)
|
|
|
|
tx.ValidUntilBlock = height + transaction.MaxValidUntilBlockIncrement
|
|
|
|
tx.Attributes = []transaction.Attribute{{
|
|
|
|
Type: transaction.OracleResponseT,
|
|
|
|
Value: resp,
|
|
|
|
}}
|
|
|
|
|
|
|
|
oracleSignContract := o.getOracleSignContract()
|
|
|
|
tx.Signers = []transaction.Signer{
|
|
|
|
{
|
2021-02-15 14:06:00 +00:00
|
|
|
Account: o.oracleHash,
|
2020-09-25 14:39:11 +00:00
|
|
|
Scopes: transaction.None,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Account: hash.Hash160(oracleSignContract),
|
|
|
|
Scopes: transaction.None,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
tx.Scripts = []transaction.Witness{
|
|
|
|
{}, // native contract witness is fixed, second witness is set later.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate network fee.
|
|
|
|
size := io.GetVarSize(tx)
|
|
|
|
tx.Scripts = append(tx.Scripts, transaction.Witness{VerificationScript: oracleSignContract})
|
|
|
|
|
|
|
|
gasConsumed, ok := o.testVerify(tx)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("can't verify transaction")
|
|
|
|
}
|
|
|
|
tx.NetworkFee += gasConsumed
|
|
|
|
|
|
|
|
netFee, sizeDelta := fee.Calculate(o.Chain.GetPolicer().GetBaseExecFee(), tx.Scripts[1].VerificationScript)
|
|
|
|
tx.NetworkFee += netFee
|
|
|
|
size += sizeDelta
|
|
|
|
|
|
|
|
currNetFee := tx.NetworkFee + int64(size)*o.Chain.FeePerByte()
|
|
|
|
if currNetFee > gasForResponse {
|
|
|
|
attrSize := io.GetVarSize(tx.Attributes)
|
|
|
|
resp.Code = transaction.InsufficientFunds
|
|
|
|
resp.Result = nil
|
|
|
|
size = size - attrSize + io.GetVarSize(tx.Attributes)
|
|
|
|
}
|
|
|
|
tx.NetworkFee += int64(size) * o.Chain.FeePerByte() // 233
|
|
|
|
|
|
|
|
// Calculate system fee.
|
|
|
|
tx.SystemFee = gasForResponse - tx.NetworkFee
|
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *Oracle) testVerify(tx *transaction.Transaction) (int64, bool) {
|
|
|
|
v := o.Chain.GetTestVM(trigger.Verification, tx, nil)
|
|
|
|
v.GasLimit = o.Chain.GetPolicer().GetMaxVerificationGAS()
|
2021-02-15 14:06:00 +00:00
|
|
|
v.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly)
|
|
|
|
v.Jump(v.Context(), o.verifyOffset)
|
2020-09-25 14:39:11 +00:00
|
|
|
|
|
|
|
ok := isVerifyOk(v)
|
|
|
|
return v.GasConsumed(), ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func isVerifyOk(v *vm.VM) bool {
|
|
|
|
if err := v.Run(); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if v.Estack().Len() != 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ok, err := v.Estack().Pop().Item().TryBool()
|
|
|
|
return err == nil && ok
|
|
|
|
}
|
2020-10-09 07:44:31 +00:00
|
|
|
|
|
|
|
func getFailedResponse(id uint64) *transaction.OracleResponse {
|
|
|
|
return &transaction.OracleResponse{
|
|
|
|
ID: id,
|
|
|
|
Code: transaction.Error,
|
|
|
|
}
|
|
|
|
}
|